Files
MikrotikManager/backend/drizzle/0043_mcp_routing_forward_charges.sql
T

63 lines
3.7 KiB
PL/PgSQL

-- Durable persistent-forward charge; no execution authority is issued.
-- Reuses the accepted B2.a immutable-parent charging rule, narrowed to ONE
-- forward charge. Existing 0040 two-slot reservations/0031 remain untouched.
-- This SQL cannot authenticate a binding or grant execution authority.
BEGIN;
DO $$ BEGIN
IF to_regclass('public.mcp_routing_forward_charges') IS NOT NULL
THEN RAISE EXCEPTION 'unmarked forward charge storage requires explicit reconciliation'; END IF;
END $$;
CREATE TABLE public.mcp_routing_forward_charges (
-- Logical historical identities, intentionally NOT lifetime FKs. INSERT is
-- checked against the actual current source below; later source/profile
-- retention cannot erase this charge or be blocked forever by old charges.
source_operation_id uuid PRIMARY KEY,
source_plan_id uuid NOT NULL,
source_step_index integer NOT NULL CHECK(source_step_index=0),
profile_id uuid NOT NULL,
connection_id uuid NOT NULL,
charge_units integer NOT NULL DEFAULT 1 CHECK(charge_units=1),
binding_sealed text NOT NULL CHECK(octet_length(binding_sealed) BETWEEN 1 AND 262144),
binding_proof text NOT NULL CHECK(binding_proof ~ '^[a-f0-9]{64}$'),
created_at timestamptz NOT NULL
);
CREATE INDEX mcp_routing_forward_charges_window
ON public.mcp_routing_forward_charges(profile_id,created_at);
CREATE INDEX mcp_routing_forward_charges_retention
ON public.mcp_routing_forward_charges(created_at,source_operation_id);
-- The immutable parent IS the charged unit. No mutable status/child can drop it
-- from the hourly projection after reject/unknown, human save or restart.
CREATE FUNCTION public.mcp_routing_forward_charge_source() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
IF NOT EXISTS(
SELECT 1 FROM public.mcp_operations o
JOIN public.mcp_plans p ON p.id=o.plan_id AND p.connection_id=o.connection_id
JOIN public.mcp_routing_profiles r ON r.id=NEW.profile_id AND r.connection_id=o.connection_id
WHERE o.id=NEW.source_operation_id AND p.id=NEW.source_plan_id
AND o.connection_id=NEW.connection_id AND o.state='pending' AND p.state='executing'
AND p.operation_id='routing.community.switch' AND p.operation_version=7
) THEN RAISE EXCEPTION 'forward charge differs from actual pending admission source'; END IF;
RETURN NEW;
END $$;
CREATE TRIGGER mcp_routing_forward_charge_source BEFORE INSERT ON public.mcp_routing_forward_charges
FOR EACH ROW EXECUTE FUNCTION public.mcp_routing_forward_charge_source();
CREATE FUNCTION public.mcp_routing_forward_charge_immutable() RETURNS trigger LANGUAGE plpgsql AS $$
DECLARE retention integer;
BEGIN
IF TG_OP<>'DELETE' THEN RAISE EXCEPTION 'immutable forward charge; rewrite/refund forbidden'; END IF;
SELECT requests_retention_days INTO retention FROM public.mcp_settings WHERE id=1 FOR SHARE;
IF retention IS NULL OR retention NOT BETWEEN 1 AND 365
OR OLD.created_at>clock_timestamp()-interval '1 hour'
OR OLD.created_at>=clock_timestamp()-make_interval(days=>retention)
OR EXISTS(SELECT 1 FROM public.mcp_operations WHERE id=OLD.source_operation_id AND state IN('pending','running'))
THEN RAISE EXCEPTION 'forward charge is still retained or unresolved'; END IF;
RETURN OLD;
END $$;
CREATE TRIGGER mcp_routing_forward_charge_immutable BEFORE UPDATE OR DELETE ON public.mcp_routing_forward_charges
FOR EACH ROW EXECUTE FUNCTION public.mcp_routing_forward_charge_immutable();
CREATE TRIGGER mcp_routing_forward_charge_no_truncate BEFORE TRUNCATE ON public.mcp_routing_forward_charges
FOR EACH STATEMENT EXECUTE FUNCTION public.mcp_routing_forward_charge_immutable();
-- DDL and exact file marker commit atomically. No SECURITY DEFINER or grants.
INSERT INTO public.schema_migrations (id) VALUES ('0043_mcp_routing_forward_charges') ON CONFLICT (id) DO NOTHING;
COMMIT;