Integrations Domain - SyncContext (Thread-Local Loop Prevention)

Overview

SyncContext is the primary loop prevention mechanism for bidirectional Django ↔ Sanity synchronization. It uses Python's thread-local storage to track sync operations within the same thread, solving the critical problem of infinite webhook loops.

Key Capabilities:

  • Thread-local storage for sync source tracking
  • Context managers for safe scope management
  • Webhook processing detection
  • Django-originated operation detection
  • Automatic context cleanup
  • Debugging utilities

Source Code Location

/home/user/reactsby_nextango/api/nextango/apps/integrations/sync/sync_context.py


The Problem SyncContext Solves

Without SyncContext (Infinite Loop):

# 1. Django Admin updates Product
product.name = "New Name"
product.save()

# 2. Signal triggers sync to Sanity
sync_product_to_sanity(product)  # Sends update to Sanity

# 3. Sanity webhook fires back to Django
handle_product_webhook(webhook_data)  # Receives webhook from Sanity

# 4. Django processes webhook, updates Product
product.name = "New Name"  # Same data
product.save()  # Save triggers signal again

# 5. Signal triggers sync to Sanity AGAIN
sync_product_to_sanity(product)  # INFINITE LOOP

# 6. Sanity webhook fires back to Django AGAIN
# ... loop continues forever

With SyncContext (Loop Prevented):

# 1. Webhook arrives
with sync_context('sanity_webhook'):  # Set thread-local context
    # 2. Process webhook
    product.name = "New Name"
    product.save()  # Triggers signal

    # 3. Signal checks SyncContext
    if should_skip_sync_to_sanity():  # Returns True (we're in webhook)
        return  # SKIP sync back to Sanity

# 4. Context cleared automatically
# 5. Normal Django operations can now sync to Sanity

Thread-Local Storage

Source: sync_context.py:10-11 Implementation:

import threading
_thread_local = threading.local()

How It Works:

  • Each thread has its own isolated storage
  • Data stored in one thread is NOT visible to other threads
  • Survives across Django ORM operations (save, refresh_from_db)
  • Automatically cleaned up when thread terminates

Why This Matters: Instance attributes like product._sync_source are lost when Django refreshes the instance from the database. Thread-local storage persists across these operations within the same request thread.


SyncContext Class

Source: sync_context.py:14-93 Purpose: Static utility class for managing thread-local sync context Pattern: Stateless utility with static methods only

Core Methods

get_sync_source()

Source: sync_context.py:23-25 Purpose: Retrieve current sync source from thread-local storage Returns: Optional[str] - sync source or None

Method Signature:

@staticmethod
def get_sync_source() -> Optional[str]:
    return getattr(_thread_local, 'sync_source', None)

Usage Example:

from nextango.apps.integrations.sync.sync_context import SyncContext

# Check current sync source
source = SyncContext.get_sync_source()

if source == 'sanity_webhook':
    print("Currently processing a webhook")
elif source is None:
    print("Normal Django operation")

set_sync_source()

Source: sync_context.py:27-31 Purpose: Set sync source in thread-local storage Side Effect: Logs debug message

Method Signature:

@staticmethod
def set_sync_source(source: str) -> None:
    _thread_local.sync_source = source
    logger.debug(f"Set thread-local sync_source: {source}")

Common Values:

  • 'sanity_webhook' - Processing Sanity webhook (inbound)
  • 'django' - Django-originated operation (outbound)
  • 'user_domain_sync_from_sanity' - User domain inbound sync
  • 'product_domain_sync_from_sanity' - Product domain inbound sync
  • 'skip_sync' - Explicitly disable sync

Usage Example:

# In webhook handler
SyncContext.set_sync_source('sanity_webhook')

# All subsequent signal handlers will see this source
product.save()  # Signal sees sync_source='sanity_webhook', skips outbound sync

clear_sync_source()

Source: sync_context.py:33-39 Purpose: Remove sync source from thread-local storage Side Effect: Logs debug message with old value

Method Signature:

@staticmethod
def clear_sync_source() -> None:
    if hasattr(_thread_local, 'sync_source'):
        old_source = _thread_local.sync_source
        delattr(_thread_local, 'sync_source')
        logger.debug(f"Cleared thread-local sync_source: {old_source}")

Usage Example:

# After webhook processing complete
SyncContext.clear_sync_source()

# Now normal Django operations can sync again
product.save()  # Signal will sync to Sanity (no longer in webhook context)

get_context_data()

Source: sync_context.py:41-48 Purpose: Get all context data for debugging Returns: Dict[str, Any]

Method Signature:

@staticmethod
def get_context_data() -> Dict[str, Any]:
    return {
        'sync_source': getattr(_thread_local, 'sync_source', None),
        'webhook_signature': getattr(_thread_local, 'webhook_signature', None),
        'transaction_id': getattr(_thread_local, 'transaction_id', None),
    }

Usage Example:

# Debug logging in signal handler
context = SyncContext.get_context_data()
logger.info(f"Processing in context: {context}")
# Output: {'sync_source': 'sanity_webhook', 'webhook_signature': 'abc123', 'transaction_id': 'txn-456'}

set_context_data()

Source: sync_context.py:50-56 Purpose: Set multiple context values at once Pattern: Kwargs-based batch update

Method Signature:

@staticmethod
def set_context_data(**kwargs) -> None:
    for key, value in kwargs.items():
        if value is not None:
            setattr(_thread_local, key, value)
            logger.debug(f"Set thread-local {key}: {value}")

Usage Example:

# Set multiple context values
SyncContext.set_context_data(
    sync_source='sanity_webhook',
    webhook_signature='sha256_abc123',
    transaction_id='txn-789'
)

# All values now available in thread-local storage

clear_context()

Source: sync_context.py:58-64 Purpose: Clear all context data (complete reset)

Method Signature:

@staticmethod
def clear_context() -> None:
    for attr in ['sync_source', 'webhook_signature', 'transaction_id']:
        if hasattr(_thread_local, attr):
            delattr(_thread_local, attr)
    logger.debug("Cleared all thread-local context")

Usage Example:

# Complete cleanup after webhook processing
SyncContext.clear_context()

# All context values now None
assert SyncContext.get_sync_source() is None
assert SyncContext.get_context_data() == {'sync_source': None, 'webhook_signature': None, 'transaction_id': None}

is_webhook_processing()

Source: sync_context.py:66-87 Purpose: Detect if currently processing a webhook from Sanity Returns: Boolean Pattern: Multi-indicator detection

Method Signature:

@staticmethod
def is_webhook_processing() -> bool:

Detection Logic (sync_context.py:74-85):

webhook_indicators = [
    'sanity_webhook',
    'webhook_processing',
    'user_domain_sync_from_sanity',
    'product_domain_sync_from_sanity',
    'store_domain_sync_from_sanity',
    'transaction_domain_sync_from_sanity',
    'promotional_campaign_domain_sync_from_sanity',
    'domain_sync_from_sanity',  # Generic domain sync from Sanity
    'hardware_sync_sanity',     # Hardware sync operations from Sanity
    'from_sanity'  # Any sync operation coming FROM Sanity
]

return any(indicator in sync_source for indicator in webhook_indicators)

Usage Example:

from nextango.apps.integrations.sync.sync_context import SyncContext

# In signal handler
if SyncContext.is_webhook_processing():
    logger.info("Webhook processing detected, skipping outbound sync")
    return  # Don't sync back to Sanity

# Normal Django operation, proceed with sync
sync_to_sanity(instance)

Why Partial Matching: Uses in operator (not exact equality) to catch all webhook-related contexts:

  • 'user_domain_sync_from_sanity' contains 'from_sanity' → detected
  • 'webhook_processing_user' contains 'webhook_processing' → detected

is_django_originated()

Source: sync_context.py:89-92 Purpose: Check if operation originated from Django (not a webhook) Returns: Boolean

Method Signature:

@staticmethod
def is_django_originated() -> bool:
    return SyncContext.get_sync_source() is None or SyncContext.get_sync_source() == 'django'

Logic:

  • None = No context set = normal Django operation = originated from Django
  • 'django' = Explicitly marked as Django operation

Usage Example:

if SyncContext.is_django_originated():
    # This change came from Django admin, API, or internal code
    # Safe to sync to Sanity
    sync_to_sanity(instance)
else:
    # This change came from webhook or other external source
    # Do NOT sync back
    logger.info("External source detected, skipping sync")

Context Managers

sync_context()

Source: sync_context.py:95-127 Purpose: General-purpose context manager for setting sync source and context data Pattern: Context manager with automatic cleanup and restoration

Function Signature:

@contextmanager
def sync_context(source: str, **context_data):

Parameters:

  • source (str): Sync source identifier
  • **context_data: Additional context key-value pairs

Behavior (sync_context.py:109-126):

# Store previous context
previous_context = SyncContext.get_context_data()

try:
    # Set new context
    SyncContext.set_sync_source(source)
    SyncContext.set_context_data(**context_data)

    logger.info(f"Entered sync context: source={source}, context={context_data}")
    yield

finally:
    # Restore previous context
    SyncContext.clear_context()
    if previous_context['sync_source'] is not None:
        SyncContext.set_context_data(**previous_context)

    logger.info(f"Exited sync context: source={source}")

Key Feature: Nested context support - restores previous context on exit

Usage Example:

from nextango.apps.integrations.sync.sync_context import sync_context

# Basic usage
with sync_context('sanity_webhook'):
    # Process webhook
    user.save()  # Signals see sync_source='sanity_webhook'

# Context automatically cleared

# With additional context data
with sync_context('sanity_webhook', webhook_signature='abc123', transaction_id='txn-456'):
    process_webhook_payload(data)

# Nested contexts (advanced)
with sync_context('django'):  # Outer context
    product.save()

    with sync_context('sanity_webhook'):  # Inner context overrides
        user.save()  # Sees 'sanity_webhook'

    # Outer context restored here
    store.save()  # Sees 'django'

webhook_processing_context()

Source: sync_context.py:129-144 Purpose: Specialized context manager for webhook processing Pattern: Wrapper around sync_context() with preset source

Function Signature:

@contextmanager
def webhook_processing_context(webhook_signature: str = None, transaction_id: str = None):

Parameters:

  • webhook_signature (str, optional): Webhook signature for verification
  • transaction_id (str, optional): Transaction ID for ping-pong prevention

Implementation (sync_context.py:143):

with sync_context('sanity_webhook', webhook_signature=webhook_signature, transaction_id=transaction_id):
    yield

Usage Example:

from nextango.apps.integrations.sync.sync_context import webhook_processing_context

# In webhook endpoint view
def handle_product_webhook(request):
    webhook_signature = request.headers.get('X-Sanity-Signature')
    payload = json.loads(request.body)

    with webhook_processing_context(
        webhook_signature=webhook_signature,
        transaction_id=payload.get('transactionId')
    ):
        # All signal handlers will see sync_source='sanity_webhook'
        # and can access webhook_signature and transaction_id from context
        process_product_webhook(payload)

    return JsonResponse({'status': 'success'})

django_sync_context()

Source: sync_context.py:147-158 Purpose: Context manager for Django-originated sync operations Pattern: Wrapper around sync_context() with 'django' source

Function Signature:

@contextmanager
def django_sync_context():

Implementation (sync_context.py:157):

with sync_context('django'):
    yield

Usage Example:

from nextango.apps.integrations.sync.sync_context import django_sync_context

# Manual sync operation from management command
def sync_all_products_to_sanity():
    with django_sync_context():
        for product in Product.objects.all():
            product.save()  # Triggers signal, syncs to Sanity
            # Signals see sync_source='django', proceed with outbound sync

Utility Functions

should_skip_sync_to_sanity()

Source: sync_context.py:161-189 Purpose: Primary decision function for skipping Django→Sanity sync Returns: Boolean Pattern: Multi-layer detection logic

Function Signature:

def should_skip_sync_to_sanity() -> bool:

Detection Logic (sync_context.py:168-188):

sync_source = SyncContext.get_sync_source()

if not sync_source:
    return False  # No context = normal operation = don't skip

# Skip if we're processing a webhook (any webhook-related sync source)
webhook_indicators = [
    'sanity_webhook',
    'webhook_processing',
    'from_sanity'  # Any sync operation coming FROM Sanity should not sync back
]

if any(indicator in sync_source for indicator in webhook_indicators):
    logger.debug(f"Skipping Django→Sanity sync: webhook processing context ({sync_source})")
    return True

# Skip if explicitly marked to skip
if sync_source == 'skip_sync':
    logger.debug("Skipping Django→Sanity sync: explicitly disabled")
    return True

return False

Usage Example:

from nextango.apps.integrations.sync.sync_context import should_skip_sync_to_sanity

# In Django signal handler (users/signals.py, products/signals.py, etc.)
@receiver(post_save, sender=Product)
def sync_product_to_sanity(sender, instance, created, **kwargs):
    # Primary loop prevention check
    if should_skip_sync_to_sanity():
        logger.info(f"Skipping Product→Sanity sync for {instance.pk}")
        return  # Skip outbound sync during webhook processing

    # Safe to sync to Sanity
    sanity_client.mutate_document(instance.to_sanity_format())

Integration Pattern: This function is called in EVERY signal handler across all domains:

  • users/signals.py: sync_user_to_sanity()
  • products/signals.py: sync_product_to_sanity()
  • stores/signals.py: sync_store_to_sanity()
  • transactions/signals.py: sync_transaction_to_sanity()

should_skip_django_processing()

Source: sync_context.py:192-206 Purpose: Check if Django processing should be skipped (future use) Returns: Boolean Status: Placeholder for future functionality

Function Signature:

def should_skip_django_processing() -> bool:

Current Implementation (sync_context.py:199-204):

sync_source = SyncContext.get_sync_source()

# Skip if we're in a Django sync operation
if sync_source == 'django':
    logger.debug("Skipping Django processing: Django sync context")
    return True

return False

Intended Use Case: When syncing FROM Sanity TO Django, might want to skip certain Django-side processing (validations, triggers, etc.) that would normally occur.

Current Status: Not widely used yet, reserved for future enhancements.


get_sync_context_info()

Source: sync_context.py:209-222 Purpose: Get comprehensive context information for debugging Returns: Dict[str, Any]

Function Signature:

def get_sync_context_info() -> Dict[str, Any]:

Response Structure (sync_context.py:216-221):

context = SyncContext.get_context_data()
context['thread_id'] = threading.current_thread().ident
context['is_webhook_processing'] = SyncContext.is_webhook_processing()
context['is_django_originated'] = SyncContext.is_django_originated()
context['should_skip_sync_to_sanity'] = should_skip_sync_to_sanity()

return context

Usage Example:

from nextango.apps.integrations.sync.sync_context import get_sync_context_info

# Debugging in signal handler
def sync_product_to_sanity(sender, instance, created, **kwargs):
    context_info = get_sync_context_info()
    logger.info(f"Sync context: {context_info}")
    # Output: {
    #     'sync_source': 'sanity_webhook',
    #     'webhook_signature': 'abc123',
    #     'transaction_id': 'txn-456',
    #     'thread_id': 140234567890,
    #     'is_webhook_processing': True,
    #     'is_django_originated': False,
    #     'should_skip_sync_to_sanity': True
    # }

    if context_info['should_skip_sync_to_sanity']:
        return

Legacy Support Functions

set_webhook_context()

Source: sync_context.py:226-232 Purpose: Legacy function for setting webhook context Pattern: Convenience wrapper for backward compatibility

Function Signature:

def set_webhook_context(webhook_signature: str = None, transaction_id: str = None):
    SyncContext.set_context_data(
        sync_source='sanity_webhook',
        webhook_signature=webhook_signature,
        transaction_id=transaction_id
    )

Usage: Prefer webhook_processing_context() context manager over this function.


clear_webhook_context()

Source: sync_context.py:235-237 Purpose: Legacy function for clearing webhook context Pattern: Wrapper around SyncContext.clear_context()

Function Signature:

def clear_webhook_context():
    SyncContext.clear_context()

Usage: Prefer context managers (automatic cleanup) over manual clearing.


Complete Integration Examples

Webhook Processing (Inbound Sync)

Full Flow:

from nextango.apps.integrations.sync.sync_context import webhook_processing_context
from nextango.apps.integrations.models import WebhookEvent, OutboundMutationLog

def handle_product_webhook(request):
    payload = json.loads(request.body)
    webhook_signature = request.headers.get('X-Sanity-Signature')

    # 1. Create webhook event
    webhook_event = WebhookEvent.objects.create(
        sanity_id=payload['_id'],
        document_type=payload['_type'],
        revision=payload['_rev'],
        payload=payload,
        status='received'
    )

    # 2. Check ping-pong prevention (database layer)
    if OutboundMutationLog.is_django_mutation(
        sanity_id=payload['_id'],
        transaction_id=payload.get('transactionId')
    ):
        webhook_event.transition_to_status('completed',
            metadata={'reason': 'ping-pong_prevented_db'})
        return JsonResponse({'status': 'skipped', 'reason': 'recent_django_mutation'})

    # 3. Process in webhook context (thread-local layer)
    with webhook_processing_context(
        webhook_signature=webhook_signature,
        transaction_id=payload.get('transactionId')
    ):
        # Mark processing started
        webhook_event.transition_to_status('processing')

        try:
            # 4. Sync from Sanity to Django
            product, created = Product.objects.update_or_create(
                sanity_id=payload['_id'],
                defaults={
                    'name': payload.get('name'),
                    'price': payload.get('price'),
                    # ... other fields
                }
            )

            # 5. post_save signal fires
            # Signal handler checks: should_skip_sync_to_sanity()
            # Returns True (we're in webhook_processing_context)
            # SKIPS sync back to Sanity (loop prevented)

            # 6. Mark complete
            webhook_event.transition_to_status('completed')

            action = 'created' if created else 'updated'
            return JsonResponse({
                'status': 'success',
                'action': action,
                'product_id': str(product.id)
            })

        except Exception as e:
            webhook_event.increment_retry(error_message=str(e))
            return JsonResponse({'status': 'error', 'message': str(e)}, status=500)

    # Context automatically cleared here

Django-Originated Sync (Outbound)

Full Flow:

from nextango.apps.integrations.sync.sync_context import should_skip_sync_to_sanity
from nextango.apps.integrations.models import OutboundMutationLog

@receiver(post_save, sender=Product)
def sync_product_to_sanity(sender, instance, created, **kwargs):
    """Signal handler for Product changes"""

    # 1. Check thread-local context (primary loop prevention)
    if should_skip_sync_to_sanity():
        logger.info(f"Skipping Product→Sanity sync: webhook processing context")
        return  # In webhook context, skip outbound sync

    # 2. Check if model has skip flag (instance-level override)
    if hasattr(instance, '_skip_sanity_sync') and instance._skip_sanity_sync:
        logger.info(f"Skipping Product→Sanity sync: instance flag set")
        return

    # 3. Safe to proceed with outbound sync
    try:
        # Prepare data for Sanity
        sanity_data = {
            '_id': instance.sanity_id,
            '_type': 'product',
            'name': instance.name,
            'price': float(instance.price),
            # ... other fields
        }

        # 4. Send to Sanity
        sanity_client = SanityAPIClient()
        result = sanity_client.mutate_document(sanity_data)

        # 5. Record mutation (database-layer ping-pong prevention)
        OutboundMutationLog.objects.create(
            sanity_id=instance.sanity_id,
            transaction_id=result.get('transactionId'),
            mutation_type='update' if not created else 'create'
        )

        logger.info(f"Successfully synced Product {instance.sanity_id} to Sanity")

    except Exception as e:
        logger.error(f"Failed to sync Product {instance.sanity_id} to Sanity: {e}")
        # Don't raise - let the save succeed even if sync fails

Management Command with Explicit Context

Full Flow:

from django.core.management.base import BaseCommand
from nextango.apps.integrations.sync.sync_context import django_sync_context

class Command(BaseCommand):
    help = 'Manually sync all products to Sanity'

    def handle(self, *args, **options):
        synced_count = 0
        failed_count = 0

        # Use django_sync_context to mark as Django-originated
        with django_sync_context():
            for product in Product.objects.all():
                try:
                    # Save triggers signal
                    # Signal sees sync_source='django'
                    # should_skip_sync_to_sanity() returns False
                    # Proceeds with outbound sync
                    product.save()
                    synced_count += 1

                    if synced_count % 100 == 0:
                        self.stdout.write(f"Synced {synced_count} products...")

                except Exception as e:
                    self.stderr.write(f"Failed to sync {product.sanity_id}: {e}")
                    failed_count += 1

        # Context cleared automatically here

        self.stdout.write(self.style.SUCCESS(
            f"Sync complete: {synced_count} succeeded, {failed_count} failed"
        ))

Cross-Domain Usage

SyncContext is used in ALL domain signal handlers:

Users Domain

Source: users/signals.py:145-159

@receiver(post_save, sender=User)
def sync_user_to_sanity(sender, instance, created, **kwargs):
    from nextango.apps.integrations.sync.sync_context import should_skip_sync_to_sanity

    if should_skip_sync_to_sanity():
        sync_source = SyncContext.get_sync_source()
        logger.info(f"Skipping User→Sanity sync: {sync_source}")
        return

    # Proceed with sync...

Products Domain

Source: products/signals.py

@receiver(post_save, sender=Product)
def sync_product_to_sanity(sender, instance, created, **kwargs):
    from nextango.apps.integrations.sync.sync_context import should_skip_sync_to_sanity

    if should_skip_sync_to_sanity():
        return

    # Proceed with sync...

Stores Domain

Source: stores/signals.py

@receiver(post_save, sender=Store)
def sync_store_to_sanity(sender, instance, created, **kwargs):
    from nextango.apps.integrations.sync.sync_context import should_skip_sync_to_sanity

    if should_skip_sync_to_sanity():
        return

    # Proceed with sync...

Pattern: Every domain follows the same pattern for consistent loop prevention.


Debugging and Troubleshooting

Enable Debug Logging

# settings.py
LOGGING = {
    'loggers': {
        'nextango.apps.integrations.sync.sync_context': {
            'level': 'DEBUG',
            'handlers': ['console'],
        },
    },
}

Debug Output Example:

DEBUG Set thread-local sync_source: sanity_webhook
INFO Entered sync context: source=sanity_webhook, context={'webhook_signature': 'abc123'}
DEBUG Skipping Django→Sanity sync: webhook processing context (sanity_webhook)
INFO Exited sync context: source=sanity_webhook
DEBUG Cleared thread-local sync_source: sanity_webhook

Common Issues

Issue: Sync loop still occurring

Diagnosis:

# Add to signal handler
from nextango.apps.integrations.sync.sync_context import get_sync_context_info

context_info = get_sync_context_info()
logger.error(f"LOOP DETECTED - Context: {context_info}")

Check:

  1. Is should_skip_sync_to_sanity() being called?
  2. Is webhook handler setting context properly?
  3. Is context being cleared prematurely?

Issue: Legitimate syncs being skipped

Diagnosis:

if should_skip_sync_to_sanity():
    context = SyncContext.get_sync_source()
    logger.warning(f"Sync skipped unexpectedly - source: {context}")
    return

Check:

  1. Is there a stale context from previous operation?
  2. Is context manager missing finally block for cleanup?
  3. Is thread being reused with leftover context?

Issue: Context not working across Django ORM operations

Solution: This is WHY we use thread-local storage!

# WRONG: Instance attribute lost on refresh
product._sync_source = 'sanity_webhook'
product.save()
product.refresh_from_db()  # _sync_source is GONE

# CORRECT: Thread-local survives refresh
SyncContext.set_sync_source('sanity_webhook')
product.save()
product.refresh_from_db()  # Context still available
assert SyncContext.get_sync_source() == 'sanity_webhook'

Best Practices

1. Always Use Context Managers

# GOOD: Automatic cleanup
with webhook_processing_context(webhook_signature=sig):
    process_webhook(data)
# Context cleared automatically

# BAD: Manual management (error-prone)
SyncContext.set_sync_source('sanity_webhook')
process_webhook(data)
SyncContext.clear_sync_source()  # What if exception occurs?

2. Check Context in ALL Signal Handlers

# GOOD: First thing in signal handler
@receiver(post_save, sender=Product)
def sync_product_to_sanity(sender, instance, created, **kwargs):
    if should_skip_sync_to_sanity():
        return
    # ... rest of handler

# BAD: No context check
@receiver(post_save, sender=Product)
def sync_product_to_sanity(sender, instance, created, **kwargs):
    sync_to_sanity(instance)  # Will cause loops!

3. Use Specific Context Names

# GOOD: Descriptive source names
with sync_context('user_domain_sync_from_sanity'):
    process_user_webhook(data)

# BAD: Generic names
with sync_context('sync'):
    process_user_webhook(data)

4. Log Context for Debugging

# GOOD: Log context when making decisions
if should_skip_sync_to_sanity():
    context = SyncContext.get_sync_source()
    logger.info(f"Skipping sync - context: {context}")
    return

# BAD: Silent skip (hard to debug)
if should_skip_sync_to_sanity():
    return

  • models.md: OutboundMutationLog (database-layer ping-pong prevention)
  • signals.md: Integration with domain signal handlers
  • services.md: Universal sync coordinator using SyncContext
  • views.md: Webhook endpoints setting context

Cross-Domain References:

  • users/signals.md: Uses should_skip_sync_to_sanity() in all sync signals
  • products/signals.md: Uses should_skip_sync_to_sanity() in all sync signals
  • stores/signals.md: Uses should_skip_sync_to_sanity() in all sync signals
  • transactions/signals.md: Uses should_skip_sync_to_sanity() in all sync signals

Was this page helpful?