sync method

Future<SyncOutcome> sync(
  1. String behavior,
  2. Future operation()
)

Sync operation (a touchpoint invocation) under the retry policy.

Implementation

Future<SyncOutcome> sync(
  String behavior,
  Future<dynamic> Function() operation,
) async {
  final startMs = clock.elapsedMs;
  final failures = <SyncFailure>[];
  var repaired = false;
  Object? lastError;

  for (var attempt = 1; attempt <= policy.maxAttempts; attempt++) {
    try {
      final result = await operation();

      // Partial-write detection: the write half-landed.
      if (result is Map && result[kPartialWriteMarker] == true) {
        failures.add(
          SyncFailure('partial', 'partial-write', atAttempt: attempt),
        );
        if (policy.repairPartialWrites) {
          // Repair: re-push once (the next loop iteration re-invokes).
          repaired = true;
          if (attempt < policy.maxAttempts) {
            _waitBackoff(attempt);
            continue;
          }
          return _outcome(
            behavior,
            succeeded: false,
            attempts: attempt,
            result: null,
            failures: failures,
            startMs: startMs,
            repaired: repaired,
            stoppedBy: 'partial-write (budget exhausted while repairing)',
          );
        }
        return _outcome(
          behavior,
          succeeded: false,
          attempts: attempt,
          result: null,
          failures: failures,
          startMs: startMs,
          repaired: false,
          stoppedBy: 'partial-write (repair disabled)',
        );
      }

      // Clean write: success with the complete ledger.
      return _outcome(
        behavior,
        succeeded: true,
        attempts: attempt,
        result: result,
        failures: failures,
        startMs: startMs,
        repaired: repaired,
        stoppedBy: null,
      );
    } on SimulatedAuthException catch (e) {
      // Auth-class failures surface honestly — never a blind retry
      // loop against an expired session (the auth-expiry storm class).
      failures.add(SyncFailure('auth', 'auth-${e.code}', atAttempt: attempt));
      return _outcome(
        behavior,
        succeeded: false,
        attempts: attempt,
        result: null,
        failures: failures,
        startMs: startMs,
        repaired: repaired,
        stoppedBy: 'auth-${e.code}',
      );
    } on SimulatedHttpException catch (e) {
      // Retryable class: network flaps and transport failures.
      lastError = e;
      failures.add(
        SyncFailure('http', 'http-${e.statusCode}', atAttempt: attempt),
      );
      if (attempt < policy.maxAttempts) {
        _waitBackoff(attempt);
        continue;
      }
    } catch (e) {
      // Other retryable transport errors.
      lastError = e;
      failures.add(SyncFailure('http', 'transport', atAttempt: attempt));
      if (attempt < policy.maxAttempts) {
        _waitBackoff(attempt);
        continue;
      }
    }
  }

  return _outcome(
    behavior,
    succeeded: false,
    attempts: policy.maxAttempts,
    result: null,
    failures: failures,
    startMs: startMs,
    repaired: repaired,
    stoppedBy: 'budget-exhausted (${lastError ?? "retry budget"})',
  );
}