mirror of
https://github.com/we-promise/sure.git
synced 2026-05-30 07:49:01 +00:00
fix(goals): current_balance guards against linked-account currency drift
Ruby idiom audit edge case. `linked_accounts.sum { |a| a.balance.to_d }`
trusted the model's validation that all linked accounts share the
goal's currency. The invariant holds at write-time, but direct DB
writes, an account-currency edit outside goal validation, or future
code that bypasses the validation chain could drift it. The naive
sum would silently add raw EUR + USD numbers and surface the result
as goal.currency.
Filter `linked_accounts.select { |a| a.currency == currency }` and
log/report-to-Sentry when the filtered count differs. The sum stays
correct (no FX, no mixing) and the operator gets visibility into
the drift.
Same pattern as `Family#savings_inflow_velocity` already uses for
the family-level rollup.
This commit is contained in:
@@ -61,11 +61,22 @@ class Goal < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
# Balance is the live balance of every linked depository account.
|
||||
# v1: single linked account in practice. v1.1+: minus other goals' allocations
|
||||
# via the upcoming GoalBacking query.
|
||||
# Balance is the live balance of every linked depository account that
|
||||
# matches the goal's currency. The model validates this invariant at
|
||||
# write time, but defensive filter + telemetry here guards against any
|
||||
# drift caused by direct DB writes, account-currency edits outside
|
||||
# goal validation, or future code that bypasses the validation chain.
|
||||
# v1.1+: minus other goals' allocations via the upcoming GoalBacking
|
||||
# query.
|
||||
def current_balance
|
||||
@current_balance ||= linked_accounts.sum { |a| a.balance.to_d }
|
||||
@current_balance ||= begin
|
||||
matching = linked_accounts.select { |a| a.currency == currency }
|
||||
if matching.size != linked_accounts.size
|
||||
Rails.logger.warn("Goal##{id} linked-account currency drift: #{linked_accounts.size - matching.size} of #{linked_accounts.size} mismatched (expected #{currency})")
|
||||
Sentry.capture_message("Goal linked-account currency drift", level: :warning, extra: { goal_id: id, expected_currency: currency }) if defined?(Sentry)
|
||||
end
|
||||
matching.sum { |a| a.balance.to_d }
|
||||
end
|
||||
end
|
||||
|
||||
def current_balance_money
|
||||
|
||||
Reference in New Issue
Block a user