From 032db2a4ba3a0fef19e508cc86e12fc0779afd01 Mon Sep 17 00:00:00 2001 From: Moritz Date: Mon, 15 Dec 2025 12:21:22 +0100 Subject: [PATCH] fix: implement fail-closed behavior in ValidateSameInterval Change validation to fail closed instead of fail open when types cannot be loaded. This prevents inconsistent data states and provides clearer error messages to users. --- .../changes/validate_same_interval.ex | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/membership_fees/changes/validate_same_interval.ex b/lib/membership_fees/changes/validate_same_interval.ex index 213efb8..db2ff3f 100644 --- a/lib/membership_fees/changes/validate_same_interval.ex +++ b/lib/membership_fees/changes/validate_same_interval.ex @@ -62,10 +62,10 @@ defmodule Mv.MembershipFees.Changes.ValidateSameInterval do add_interval_mismatch_error(changeset, current_interval, new_interval) end - {:error, _reason} -> - # If we can't load the types, allow the change (fail open) - # The database constraint will catch invalid foreign keys - changeset + {:error, reason} -> + # Fail closed: If we can't load the types, reject the change + # This prevents inconsistent data states + add_type_validation_error(changeset, reason) end end @@ -114,6 +114,17 @@ defmodule Mv.MembershipFees.Changes.ValidateSameInterval do ) end + # Add validation error when types cannot be loaded + defp add_type_validation_error(changeset, reason) do + message = "Could not validate membership fee type intervals: type not found" + + Ash.Changeset.add_error( + changeset, + field: :membership_fee_type_id, + message: message + ) + end + # Format interval atom to human-readable string defp format_interval(:monthly), do: "monthly" defp format_interval(:quarterly), do: "quarterly"