5.8 KiB
Agent Rules
These rules are MANDATORY and MUST be followed without exception by all agents working on this codebase.
1. Entity Versioning MUST Be Automatic
Caution
NON-NEGOTIABLE REQUIREMENT
All versioned entities (Parks, Rides, Companies, etc.) MUST have their version history created automatically via Django signals—NEVER manually in views, serializers, or management commands.
Why This Rule Exists
Manual version creation is fragile and error-prone. If any code path modifies a versioned entity without explicitly calling "create version," history is lost forever. Signal-based versioning guarantees that every single modification is captured, regardless of where the change originates.
Required Implementation Pattern
# apps/{entity}/signals.py - REQUIRED for all versioned entities
from django.db.models.signals import pre_save
from django.dispatch import receiver
from .models import Park, ParkVersion
from .serializers import ParkSerializer
@receiver(pre_save, sender=Park)
def auto_create_park_version(sender, instance, **kwargs):
"""
Automatically snapshot the entity BEFORE any modification.
This signal fires for ALL save operations, ensuring no history is ever lost.
"""
if not instance.pk:
return # Skip for initial creation
try:
old_instance = sender.objects.get(pk=instance.pk)
ParkVersion.objects.create(
park=instance,
data=ParkSerializer(old_instance).data,
changed_by=getattr(instance, '_changed_by', None),
change_summary=getattr(instance, '_change_summary', 'Auto-versioned')
)
except sender.DoesNotExist:
pass # Edge case: concurrent deletion
Passing Context to Signals
When updating entities, attach metadata to the instance before saving:
# In views/serializers - attach context, DON'T create versions manually
park._changed_by = request.user
park._change_summary = submission_note or "Updated via API"
park.save() # Signal handles versioning automatically
What Is FORBIDDEN
The following patterns are strictly prohibited and will be flagged as non-compliant:
# ❌ FORBIDDEN: Manual version creation in views
def update(self, request, slug=None):
park = self.get_object()
# ... update logic ...
ParkVersion.objects.create(park=park, ...) # VIOLATION!
park.save()
# ❌ FORBIDDEN: Manual version creation in serializers
def update(self, instance, validated_data):
ParkVersion.objects.create(park=instance, ...) # VIOLATION!
return super().update(instance, validated_data)
# ❌ FORBIDDEN: Manual version creation in management commands
def handle(self, *args, **options):
for park in Park.objects.all():
ParkVersion.objects.create(park=park, ...) # VIOLATION!
park.status = 'updated'
park.save()
Compliance Checklist
For every versioned entity, verify:
- A
pre_savesignal receiver exists insignals.py - The signal is connected in
apps.pyviaready()method - No manual
{Entity}Version.objects.create()calls exist in views - No manual
{Entity}Version.objects.create()calls exist in serializers - No manual
{Entity}Version.objects.create()calls exist in management commands
2. Error Handling MUST Use Capture Utilities
Caution
NON-NEGOTIABLE REQUIREMENT
All error-prone code on both backend AND frontend MUST use the error capture utilities. Errors should flow to the admin dashboard (/admin/errors) for monitoring.
Backend Requirements
Use the utilities from apps.core.utils:
from apps.core.utils import capture_errors, error_context, capture_and_log
# ✅ REQUIRED: Decorator on views and critical functions
@capture_errors(source='api', severity='high')
def create_park(request, data):
return ParkService.create(data)
# ✅ REQUIRED: Context manager for critical operations
with error_context('Processing payment', severity='critical'):
process_payment()
# ✅ ACCEPTABLE: Manual capture when you need the error ID
except Exception as e:
error_id = capture_and_log(e, 'Operation failed', severity='high')
Frontend Requirements
Use the composables and utilities:
// ✅ REQUIRED: Component-level error boundary
const { wrap, error, retry } = useErrorBoundary({ componentName: 'RideCard' })
const ride = await wrap(() => api.get('/rides/1'), 'Loading ride')
// ✅ REQUIRED: tryCatch for async operations
const [data, error] = await tryCatch(fetchData(), 'Fetching data')
// ✅ REQUIRED: Report caught errors
const { reportError } = useReportError()
try { ... } catch (e) { reportError(e, { action: 'Operation' }) }
What Is FORBIDDEN
# ❌ FORBIDDEN: Silent exception swallowing
except Exception:
pass # VIOLATION! Error lost forever
# ❌ FORBIDDEN: Logging without dashboard capture
except Exception as e:
logger.error(e) # VIOLATION! Not visible in dashboard
// ❌ FORBIDDEN: Silent catch
catch (e) { console.error(e) } // VIOLATION! Not in dashboard
// ❌ FORBIDDEN: Uncaptured async errors
await riskyOperation() // VIOLATION! No error handling
Compliance Checklist
- All API views decorated with
@capture_errors - All critical service methods use
error_context - All frontend async operations use
tryCatchoruseErrorBoundary - No silent exception swallowing (
except: pass) - All caught exceptions reported via utilities
Documentation
Full usage guide: docs/ERROR_HANDLING.md
Document Authority
This document has the same authority as all other source_docs/ files. Per the /comply workflow, these specifications are immutable law and must be enforced immediately upon detection of any violation.