Skip to content

Commit a76f1d3

Browse files
feat(audience): surface consent persistence failures via OnError (SDK-147)
ConsentStore.Save can fail from transient I/O (disk full, locked file, permissions). Previously the catch handler at ImmutableAudience.cs:318 logged a warning and returned silently - studios had no programmatic signal that their consent change wouldn't survive a restart. Adds AudienceErrorCode.ConsentPersistFailed, distinct from ConsentSyncFailed (backend PUT) so operators can tell the two failure modes apart: a Save failure means local state will revert on relaunch, a Sync failure means the backend audit trail is out of sync but local state is fine. In-memory behaviour is unchanged - the new level still applies to the current session, the purge/downgrade still runs. The callback just gives studios a hook to warn the player, log to their telemetry, or retry the persist at a better moment. New test pre-creates a directory at the consent file path to force File.Move to fail, then asserts ConsentPersistFailed reaches OnError. 155 passing. Linear: SDK-147 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 831d1c9 commit a76f1d3

3 files changed

Lines changed: 29 additions & 1 deletion

File tree

src/Packages/Audience/Runtime/AudienceError.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ public enum AudienceErrorCode
77
FlushFailed,
88
ValidationRejected,
99
ConsentSyncFailed,
10-
NetworkError
10+
NetworkError,
11+
/// <summary>Failed to persist the consent level to disk. In-memory level still applied but will revert on next launch.</summary>
12+
ConsentPersistFailed
1113
}
1214

1315
public class AudienceError : Exception

src/Packages/Audience/Runtime/ImmutableAudience.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,8 @@ public static void SetConsent(ConsentLevel level)
322322
{
323323
Log.Warn($"SetConsent — failed to persist consent level: {ex.GetType().Name}: {ex.Message}. " +
324324
"In-memory level is updated but will revert on next launch.");
325+
NotifyErrorCallback(config.OnError, AudienceErrorCode.ConsentPersistFailed,
326+
$"Consent persist failed: {ex.Message}");
325327
}
326328

327329
if (level == ConsentLevel.None)

src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,30 @@ public void ResetState_ClearsIdentityCache_AcrossInitWithDifferentPath()
596596
}
597597
}
598598

599+
[Test]
600+
public void SetConsent_PersistFailure_SurfacesOnError()
601+
{
602+
// Pre-create a directory where ConsentStore.Save wants to place
603+
// the consent file; File.Move then fails without disturbing
604+
// Init's DiskStore or Identity paths.
605+
var consentFile = AudiencePaths.ConsentFile(_testDir);
606+
Directory.CreateDirectory(consentFile);
607+
608+
// Bag rather than single capture: ConsentPersistFailed fires
609+
// synchronously on the caller thread, SyncConsentToBackend's
610+
// Task.Run may also fire ConsentSyncFailed concurrently. Assert
611+
// presence of the one under test rather than the last seen.
612+
var errors = new System.Collections.Concurrent.ConcurrentBag<AudienceError>();
613+
var config = MakeConfig(ConsentLevel.Anonymous);
614+
config.OnError = err => errors.Add(err);
615+
616+
ImmutableAudience.Init(config);
617+
ImmutableAudience.SetConsent(ConsentLevel.Full);
618+
619+
Assert.That(errors.Any(e => e.Code == AudienceErrorCode.ConsentPersistFailed),
620+
Is.True, "OnError should receive ConsentPersistFailed for consent persist failure");
621+
}
622+
599623
[Test]
600624
public void SetConsent_PersistsAcrossInit()
601625
{

0 commit comments

Comments
 (0)