20260818_550_metering_unified_replay_acl.py 6.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041
  1. """Unify event-time allocation replay and revoke superseded gateway paths."""
  2. from alembic import op
  3. revision = "20260818_550"
  4. down_revision = "20260818_549"
  5. branch_labels = None
  6. depends_on = None
  7. def upgrade() -> None:
  8. op.get_bind().exec_driver_sql(r'''
  9. DO $preflight$
  10. BEGIN
  11. IF EXISTS(SELECT 1 FROM public.metering_events e JOIN public.metering_events o ON o.event_uid=e.correction_of WHERE e.correction_of IS NOT NULL AND (e.tenant_ref<>o.tenant_ref OR e.domain_ref<>o.domain_ref OR o.correction_of IS NOT NULL OR e.event_kind<>o.event_kind OR e.unit<>o.unit OR e.window_start<>o.window_start OR e.window_end<>o.window_end OR e.department_ref<>o.department_ref OR e.project_ref<>o.project_ref OR e.cost_center_ref<>o.cost_center_ref)) OR EXISTS(SELECT 1 FROM public.metering_events WHERE evidence_reference !~ '^local-fixture://wp12/v1(/[a-z][a-z0-9-]{0,62})?$' OR evidence_reference ~ '(sql|script|select|insert|update|delete|drop|exec|curl|wget|token|secret|credential|password)') OR EXISTS(SELECT 1 FROM public.metering_allocation_rules a JOIN public.metering_allocation_rules b ON (a.rule_uid,a.rule_version)<>(b.rule_uid,b.rule_version) AND a.tenant_ref=b.tenant_ref AND a.domain_ref=b.domain_ref AND a.department_ref=b.department_ref AND a.project_ref=b.project_ref AND a.cost_center_ref=b.cost_center_ref AND tstzrange(a.effective_start,a.effective_end,'[)') && tstzrange(b.effective_start,b.effective_end,'[)')) OR EXISTS(SELECT 1 FROM public.metering_allocations WHERE weight_micros<=0) OR EXISTS(SELECT 1 FROM public.metering_allocations GROUP BY rule_uid,rule_version HAVING sum(weight_micros)<>1000000) THEN RAISE EXCEPTION 'upgrade refused: WP12 integrity preflight failed'; END IF;
  12. END; $preflight$;
  13. ALTER FUNCTION public.metering_showback_allocation_replay(jsonb) RENAME TO metering_showback_allocation_replay_legacy;
  14. CREATE FUNCTION public.metering_showback_allocation_replay(p_payload jsonb) RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path=pg_catalog,public AS $replay$
  15. DECLARE c record; rule record; source_micros bigint; result_allocations jsonb; outside_count bigint;
  16. BEGIN
  17. IF NOT pg_has_role(session_user,'dataops_app_runtime','MEMBER') OR jsonb_typeof(p_payload)<>'object' OR p_payload-ARRAY['request_claim','window','rule_uid','rule_version']<>'{}'::jsonb OR p_payload->>'request_claim' !~ '^[0-9a-f-]{36}$' OR p_payload->>'window' !~ '^[0-9]{4}-(0[1-9]|1[0-2])$' OR p_payload->>'rule_uid' !~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$' OR p_payload->>'rule_version' !~ '^[1-9][0-9]{0,6}$' THEN RAISE EXCEPTION 'metering_allocation_replay_denied'; END IF;
  18. SELECT * INTO c FROM public.metering_runtime_claims WHERE claim_uid=(p_payload->>'request_claim')::uuid AND action_name='read' AND consumed_at IS NULL AND expires_at>clock_timestamp() FOR UPDATE;
  19. IF NOT FOUND THEN RAISE EXCEPTION 'metering_claim_denied'; END IF;
  20. SELECT * INTO rule FROM public.metering_allocation_rules WHERE rule_uid=p_payload->>'rule_uid' AND rule_version=(p_payload->>'rule_version')::integer AND tenant_ref=c.tenant_ref AND domain_ref=c.domain_ref;
  21. IF NOT FOUND THEN RAISE EXCEPTION 'metering_allocation_rule_not_found'; END IF;
  22. SELECT count(*) FILTER (WHERE window_start<rule.effective_start OR window_start>=rule.effective_end),COALESCE(sum(quantity_micros),0) INTO outside_count,source_micros FROM public.metering_events WHERE tenant_ref=c.tenant_ref AND domain_ref=c.domain_ref AND department_ref=rule.department_ref AND project_ref=rule.project_ref AND cost_center_ref=rule.cost_center_ref AND to_char(window_start AT TIME ZONE 'UTC','YYYY-MM')=p_payload->>'window';
  23. IF outside_count<>0 THEN RAISE EXCEPTION 'metering_allocation_replay_coverage_invalid'; END IF;
  24. WITH weights AS (SELECT a.target_ref,(source_micros*a.weight_micros/1000000)::bigint AS base_micros FROM public.metering_allocations a WHERE a.rule_uid=rule.rule_uid AND a.rule_version=rule.rule_version), totals AS (SELECT COALESCE(sum(base_micros),0) AS base_total,min(target_ref) AS residual_target FROM weights) SELECT COALESCE(jsonb_agg(jsonb_build_object('target',w.target_ref,'quantity_micros',w.base_micros+CASE WHEN w.target_ref=t.residual_target THEN source_micros-t.base_total ELSE 0 END) ORDER BY w.target_ref),'[]'::jsonb) INTO result_allocations FROM weights w CROSS JOIN totals t;
  25. UPDATE public.metering_runtime_claims SET consumed_at=clock_timestamp() WHERE claim_uid=c.claim_uid;
  26. INSERT INTO public.metering_audit_events(tenant_ref,domain_ref,event_type,actor_ref,payload_digest) VALUES(c.tenant_ref,c.domain_ref,'showback.read',c.principal_ref,encode(sha256(convert_to('allocation_replay_v2|'||p_payload::text,'utf8')),'hex'));
  27. RETURN jsonb_build_object('rule_uid',rule.rule_uid,'rule_version',rule.rule_version,'window',p_payload->>'window','mapping',jsonb_build_object('department',rule.department_ref,'business_domain',rule.domain_ref,'project',rule.project_ref,'cost_center',rule.cost_center_ref),'rule_digest',rule.rule_digest,'source_micros',source_micros,'allocated_micros',source_micros,'difference_micros',0,'allocations',result_allocations,'coverage','complete-event-time','mode','ENGINEERING_EVIDENCE_ONLY');
  28. END; $replay$;
  29. ALTER FUNCTION public.metering_showback_allocation_replay(jsonb) OWNER TO dataops_tenant_foundation_owner;
  30. REVOKE ALL ON FUNCTION public.metering_showback_runtime_read_legacy(text,jsonb),public.metering_showback_control_write_legacy(text,text,jsonb),public.metering_showback_allocation_replay_legacy(jsonb) FROM PUBLIC,dataops_app,dataops_app_runtime,dataops_bi_ai_catalog_control;
  31. REVOKE ALL ON FUNCTION public.metering_showback_allocation_replay(jsonb) FROM PUBLIC,dataops_app,dataops_bi_ai_catalog_control;
  32. GRANT EXECUTE ON FUNCTION public.metering_showback_allocation_replay(jsonb) TO dataops_app_runtime;
  33. ''')
  34. def downgrade() -> None:
  35. op.execute("DROP FUNCTION public.metering_showback_allocation_replay(jsonb); ALTER FUNCTION public.metering_showback_allocation_replay_legacy(jsonb) RENAME TO metering_showback_allocation_replay; GRANT EXECUTE ON FUNCTION public.metering_showback_allocation_replay(jsonb) TO dataops_app_runtime;")