SIGN IN SIGN UP

Surface attribute sync errors when fetching offerings (#4129)

## Description
Currently `syncAttributesAndOfferingsIfNeeded()` does not actually
return an error if attribute syncing fails. Which is unexpected if
you're trying to sync attributes that are expected to influence the
offerings returned.

This PR fixes this by returning the error returned from syncing
attributes, before attempting to refresh the offerings. Ensuring that
the error is surfaced correctly.

## Changes
We will now treat any error received during attribute syncing as fatal,
thus returning and not continuing with fetching offerings, with one
exception.

(Some) reserved attributes can only be set once, and any subsequent sync
of an updated value will result in an error. Therefore we will treat
these as non fatal, so if a sync results in errors only from reserved
attributes, we'll still consider it a success and continue. This of
course does not apply if any other errors are also encountered.

iOS counterpart: RevenueCat/purchases-ios#7539

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes public-facing behavior of sync-then-offerings flows: callers
may now see errors that were previously swallowed, though
reserved-attribute 7263-only cases remain non-fatal by design.
> 
> **Overview**
> **`syncAttributesAndOfferingsIfNeeded`** now fails the callback when
subscriber attribute sync hits a blocking error, instead of always
continuing to refresh offerings.
> 
> Attribute sync reports per-request outcomes through a new optional
**`syncedAttribute`** hook on
**`SubscriberAttributesManager.synchronizeSubscriberAttributesForAllUsers`**.
The orchestrator records the first blocking error and, after all syncs
finish, either invokes **`onError`** and skips offerings, or proceeds
with remote config refresh and **`getOfferings`** as before.
> 
> Blocking is skipped only for **`InvalidSubscriberAttributesError`**
(backend **7263**) when every reported attribute error is on a reserved
key (name starts with **`$`**) and carries that code. Custom attributes,
mixed errors, missing attribute details, other error codes (e.g.
**7264**), and non-attribute failures still block offerings fetch.
> 
> **`SubscriberAttributeError`** gains optional **`backendErrorCode`**,
populated when parsing attribute error JSON in **`getAttributeErrors`**
so the reserved-only exception can be applied reliably.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
672ece840ce7df0792a3ecb45c90ba1a1bf65820. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
R
Rick committed
0a50b9a773d71a6699549429dc9f71ccc57cb718
Parent: f960c31
Committed by GitHub <noreply@github.com> on 8/28/2026, 3:34:38 PM