Skip to content

fix: event and replay schemas moved to schemas folder, all other domain obj - converted to python dataclasses#179

Open
HardMax71 wants to merge 2 commits intomainfrom
fix/layer-split
Open

fix: event and replay schemas moved to schemas folder, all other domain obj - converted to python dataclasses#179
HardMax71 wants to merge 2 commits intomainfrom
fix/layer-split

Conversation

@HardMax71
Copy link
Owner

@HardMax71 HardMax71 commented Feb 14, 2026


Summary by cubic

Moved event and replay API schemas into dedicated modules and completed the switch of domain models to Python dataclasses. This tightens the layer split, simplifies serialization, and keeps API behavior stable.

  • Refactors

    • Consolidated event schemas into app/schemas_pydantic/event_schemas and replay schemas into app/schemas_pydantic/replay_schemas; ExecutionResultSummary and ReplaySession now live in replay_schemas.
    • Converted remaining domain models (users, notifications, execution, replay, saga, saved scripts, SSE, user settings, rate limit) to dataclasses; routes/services now build domains via dataclass constructors and dataclasses.replace.
    • Repositories write via dataclasses.asdict and read via doc.model_dump(include=set(Domain.dataclass_fields)); updates exclude None via dict comprehensions.
    • Idempotency for DomainExecution uses a Pydantic TypeAdapter for JSON load/validate.
    • SystemSettings domain is a dataclass with simple validation; SystemSettingsDocument stores SystemSettingsSchema, and APIs validate schemas with from_attributes=True.
    • Moved DLQTopicSummary to app/schemas_pydantic/dlq and updated imports.
  • Migration

    • Replace domain .model_validate/.model_copy/.model_dump with dataclass constructors, dataclasses.replace, dataclasses.asdict; when reading docs, use doc.model_dump(include=set(dataclass_fields)).
    • Update imports:
      • Event schemas: app/schemas_pydantic/event_schemas
      • Replay: app/schemas_pydantic/replay_schemas
      • System settings schema: app/schemas_pydantic/admin_settings
      • DLQTopicSummary: app/schemas_pydantic/dlq
    • No database changes required.

Written for commit 58756c7. Summary will update on new commits.

Summary by CodeRabbit

  • Refactor

    • Major backend model and schema rework for more consistent data handling and replay/event schema reorganization; no breaking public API changes.
    • System settings and replay flows updated internally, tightening validation and initialization behavior (validation errors now surface differently).
  • Tests

    • End-to-end and unit tests updated to align with the refactor and ensure settings/replay routes remain stable.

@coderabbitai
Copy link

coderabbitai bot commented Feb 14, 2026

📝 Walkthrough

Walkthrough

Migrates many Pydantic models to Python dataclasses and updates callsites to use dataclasses.asdict()/constructor unpacking instead of model_validate()/model_dump(); reorganizes several Pydantic schemas into new modules (notably event and replay schemas) and updates imports across routes, repositories, services, and tests.

Changes

Cohort / File(s) Summary
API Route Handlers
backend/app/api/routes/admin/events.py, backend/app/api/routes/admin/users.py, backend/app/api/routes/notifications.py, backend/app/api/routes/replay.py, backend/app/api/routes/saved_scripts.py, backend/app/api/routes/user_settings.py
Switch from model_validate()/model_dump() to constructing domain objects via (**obj.model_dump()) or dataclass constructors; replay schema import paths updated to ...replay_schemas. Review input validation/serialization changes.
Database Repositories
backend/app/db/repositories/... (admin_*, dlq_repository.py, execution_repository.py, notification_repository.py, replay_repository.py, resource_allocation_repository.py, saga_repository.py, saved_script_repository.py, sse_repository.py, user_repository.py, user_settings_repository.py)
Replaced Pydantic dumps/validations with dataclasses.asdict() for inserts/updates and explicit reconstruction from doc.model_dump(include=...) into dataclasses. Many update paths now filter None via dict comprehensions. Pay attention to include/exclude field lists and None-filtering semantics.
Domain Model Definitions
backend/app/domain/... (admin/replay_models.py, admin/replay_updates.py, admin/settings_models.py, events/* removed and re-exported, execution/models.py, notification/models.py, rate_limit/rate_limit_models.py, replay/models.py, saga/models.py, saved_script/models.py, sse/models.py, user/*)`
Converted numerous domain models from Pydantic BaseModel to @dataclass. Added __post_init__ coercion/validation for nested fields in several dataclasses and added/adjusted some fields/defaults. Review changed constructors, validation behavior, and any added fields (e.g., new defaults, new fields on UserDeleteResult, SystemSettings validation now in dataclass).
Schemas & Exports
backend/app/schemas_pydantic/event_schemas.py, backend/app/schemas_pydantic/replay_schemas.py, backend/app/schemas_pydantic/dlq.py, backend/app/schemas_pydantic/admin_settings.py, backend/app/domain/events/__init__.py, backend/app/dlq/*
Moved/added Pydantic schema modules for events and replay; removed old schemas_pydantic/events.py and schemas_pydantic/replay*.py entries. DLQTopicSummary moved to schemas_pydantic/dlq.py. Verify import updates and that consumers use the new schema locations.
Service Layer
backend/app/services/... (admin/admin_events_service.py, admin/admin_user_service.py, execution_service.py, idempotency_manager.py, result_processor/processor.py, saga/saga_orchestrator.py, user_settings_service.py)
Adapted services to dataclass usage: model_copy()dataclasses.replace(), introduced TypeAdapter for DomainExecution JSON (de)serialization, switched to dataclasses.asdict() for publishing/updates. Check mutation semantics and JSON adapter usage.
Tests & Docs
backend/tests/..., backend/app/db/docs/*, backend/app/api/routes/admin/settings.py
Updated tests and docs to construct dataclasses directly (e.g., SystemSettings(...)) and to import new replay/event schema modules. Ensure tests align with new validation error types and constructor behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

Poem

🐰 I dug through models, neat and small,
From BaseModel burrows I watched them fall.
With dataclass hops and asdict delight,
Fields align softly, constructors light.
A carrot-toast to clean imports—hip hooray!

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly describes the main changeset: migration of event/replay schemas and conversion of domain objects from Pydantic to dataclasses.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/layer-split

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 issues found across 47 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/app/schemas_pydantic/admin_settings.py">

<violation number="1" location="backend/app/schemas_pydantic/admin_settings.py:6">
P2: The CPU validation regex only allows millicore values (e.g., `500m`) and rejects valid Kubernetes quantities like `1` or `0.5`, which the docs allow. This will make legitimate CPU limits fail validation.</violation>
</file>

<file name="backend/app/schemas_pydantic/dlq.py">

<violation number="1" location="backend/app/schemas_pydantic/dlq.py:84">
P2: This new `DLQTopicSummary` duplicates `DLQTopicSummaryResponse` exactly. Keeping two identical models increases maintenance risk and ambiguity—prefer reusing the existing response model or aliasing it instead of redefining it.</violation>
</file>

<file name="backend/app/domain/user/user_models.py">

<violation number="1" location="backend/app/domain/user/user_models.py:108">
P2: After migrating from Pydantic to dataclass, `is_valid()` can crash with `TypeError` if `email` is `None`. Unlike Pydantic, dataclasses don't enforce runtime types, so `email=None` is now possible. Since `all([...])` eagerly evaluates all list elements, `EMAIL_PATTERN.match(self.email)` runs even though `self.email` is falsy. Consider short-circuiting or guarding the email pattern match.</violation>
</file>

<file name="backend/app/api/routes/saved_scripts.py">

<violation number="1" location="backend/app/api/routes/saved_scripts.py:71">
P2: `model_dump()` includes optional fields with `None` defaults, so the update payload will always carry `None` for omitted fields and a new `updated_at`. That can overwrite existing values unintentionally. Use `model_dump(exclude_unset=True)` when building the dataclass so only client-provided fields are updated.</violation>
</file>

<file name="backend/app/services/user_settings_service.py">

<violation number="1" location="backend/app/services/user_settings_service.py:197">
P1: `_apply_event` passes event metadata fields (event_id, event_type, timestamp, changed_fields, reason) into `DomainUserSettings(**...)`, which will raise a `TypeError` because those keys aren’t valid `DomainUserSettings` parameters. Filter the event dict to only settings fields before constructing the settings model.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

{**settings.model_dump(), **event.model_dump(exclude_none=True), "updated_at": event.timestamp}
)
"""Apply a settings update event via dict merge."""
return DomainUserSettings(**{
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: _apply_event passes event metadata fields (event_id, event_type, timestamp, changed_fields, reason) into DomainUserSettings(**...), which will raise a TypeError because those keys aren’t valid DomainUserSettings parameters. Filter the event dict to only settings fields before constructing the settings model.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/app/services/user_settings_service.py, line 197:

<comment>`_apply_event` passes event metadata fields (event_id, event_type, timestamp, changed_fields, reason) into `DomainUserSettings(**...)`, which will raise a `TypeError` because those keys aren’t valid `DomainUserSettings` parameters. Filter the event dict to only settings fields before constructing the settings model.</comment>

<file context>
@@ -190,10 +193,12 @@ async def restore_settings_to_point(self, user_id: str, timestamp: datetime) ->
-            {**settings.model_dump(), **event.model_dump(exclude_none=True), "updated_at": event.timestamp}
-        )
+        """Apply a settings update event via dict merge."""
+        return DomainUserSettings(**{
+            **dataclasses.asdict(settings),
+            **{k: v for k, v in dataclasses.asdict(event).items() if v is not None},
</file context>
Fix with Cubic

from app.domain.admin.settings_models import LogLevel

K8S_MEMORY_PATTERN = r"^[1-9]\d*(Ki|Mi|Gi)$"
K8S_CPU_PATTERN = r"^[1-9]\d*m$"
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The CPU validation regex only allows millicore values (e.g., 500m) and rejects valid Kubernetes quantities like 1 or 0.5, which the docs allow. This will make legitimate CPU limits fail validation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/app/schemas_pydantic/admin_settings.py, line 6:

<comment>The CPU validation regex only allows millicore values (e.g., `500m`) and rejects valid Kubernetes quantities like `1` or `0.5`, which the docs allow. This will make legitimate CPU limits fail validation.</comment>

<file context>
@@ -1,4 +1,30 @@
+from app.domain.admin.settings_models import LogLevel
+
+K8S_MEMORY_PATTERN = r"^[1-9]\d*(Ki|Mi|Gi)$"
+K8S_CPU_PATTERN = r"^[1-9]\d*m$"
+
+
</file context>
Suggested change
K8S_CPU_PATTERN = r"^[1-9]\d*m$"
K8S_CPU_PATTERN = r"^(?:[1-9]\d*(?:\.\d+)?|0\.\d+|[1-9]\d*m)$"
Fix with Cubic

Comment on lines +84 to +95
class DLQTopicSummary(BaseModel):
"""Summary of a topic in DLQ."""

model_config = ConfigDict(from_attributes=True)

topic: str
total_messages: int
status_breakdown: dict[DLQMessageStatus, int]
oldest_message: datetime
newest_message: datetime
avg_retry_count: float
max_retry_count: int
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This new DLQTopicSummary duplicates DLQTopicSummaryResponse exactly. Keeping two identical models increases maintenance risk and ambiguity—prefer reusing the existing response model or aliasing it instead of redefining it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/app/schemas_pydantic/dlq.py, line 84:

<comment>This new `DLQTopicSummary` duplicates `DLQTopicSummaryResponse` exactly. Keeping two identical models increases maintenance risk and ambiguity—prefer reusing the existing response model or aliasing it instead of redefining it.</comment>

<file context>
@@ -81,6 +81,20 @@ class DLQTopicSummaryResponse(BaseModel):
     max_retry_count: int
 
 
+class DLQTopicSummary(BaseModel):
+    """Summary of a topic in DLQ."""
+
</file context>
Suggested change
class DLQTopicSummary(BaseModel):
"""Summary of a topic in DLQ."""
model_config = ConfigDict(from_attributes=True)
topic: str
total_messages: int
status_breakdown: dict[DLQMessageStatus, int]
oldest_message: datetime
newest_message: datetime
avg_retry_count: float
max_retry_count: int
DLQTopicSummary = DLQTopicSummaryResponse
Fix with Cubic

self.email,
self.password and len(self.password) >= 8,
EMAIL_PATTERN.match(self.email) is not None, # Proper email validation
EMAIL_PATTERN.match(self.email) is not None,
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: After migrating from Pydantic to dataclass, is_valid() can crash with TypeError if email is None. Unlike Pydantic, dataclasses don't enforce runtime types, so email=None is now possible. Since all([...]) eagerly evaluates all list elements, EMAIL_PATTERN.match(self.email) runs even though self.email is falsy. Consider short-circuiting or guarding the email pattern match.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/app/domain/user/user_models.py, line 108:

<comment>After migrating from Pydantic to dataclass, `is_valid()` can crash with `TypeError` if `email` is `None`. Unlike Pydantic, dataclasses don't enforce runtime types, so `email=None` is now possible. Since `all([...])` eagerly evaluates all list elements, `EMAIL_PATTERN.match(self.email)` runs even though `self.email` is falsy. Consider short-circuiting or guarding the email pattern match.</comment>

<file context>
@@ -114,16 +105,15 @@ def is_valid(self) -> bool:
                 self.email,
                 self.password and len(self.password) >= 8,
-                EMAIL_PATTERN.match(self.email) is not None,  # Proper email validation
+                EMAIL_PATTERN.match(self.email) is not None,
             ]
         )
</file context>
Suggested change
EMAIL_PATTERN.match(self.email) is not None,
self.email and EMAIL_PATTERN.match(self.email) is not None,
Fix with Cubic

) -> SavedScriptResponse:
"""Update an existing saved script."""
update_data = DomainSavedScriptUpdate.model_validate(script_update)
update_data = DomainSavedScriptUpdate(**script_update.model_dump())
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: model_dump() includes optional fields with None defaults, so the update payload will always carry None for omitted fields and a new updated_at. That can overwrite existing values unintentionally. Use model_dump(exclude_unset=True) when building the dataclass so only client-provided fields are updated.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/app/api/routes/saved_scripts.py, line 71:

<comment>`model_dump()` includes optional fields with `None` defaults, so the update payload will always carry `None` for omitted fields and a new `updated_at`. That can overwrite existing values unintentionally. Use `model_dump(exclude_unset=True)` when building the dataclass so only client-provided fields are updated.</comment>

<file context>
@@ -68,7 +68,7 @@ async def update_saved_script(
 ) -> SavedScriptResponse:
     """Update an existing saved script."""
-    update_data = DomainSavedScriptUpdate.model_validate(script_update)
+    update_data = DomainSavedScriptUpdate(**script_update.model_dump())
     domain = await saved_script_service.update_saved_script(script_id, user.user_id, update_data)
     return SavedScriptResponse.model_validate(domain)
</file context>
Suggested change
update_data = DomainSavedScriptUpdate(**script_update.model_dump())
update_data = DomainSavedScriptUpdate(**script_update.model_dump(exclude_unset=True))
Fix with Cubic

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/app/domain/replay/models.py (1)

40-52: ⚠️ Potential issue | 🟡 Minor

is_empty() omits exclude_event_types — a filter with only exclusions would appear "empty".

exclude_event_types is not checked in is_empty(). If a ReplayFilter has only exclude_event_types set (all other fields None), is_empty() returns True, yet to_mongo_query() would produce a non-empty query with $nin. This could lead to unintended behavior if is_empty() is used to guard against running unfiltered replays.

🐛 Proposed fix
     def is_empty(self) -> bool:
         return not any(
             [
                 self.event_ids,
                 self.execution_id,
                 self.aggregate_id,
                 self.event_types,
+                self.exclude_event_types,
                 self.start_time,
                 self.end_time,
                 self.user_id,
                 self.service_name,
             ]
         )
🤖 Fix all issues with AI agents
In `@backend/app/db/repositories/sse_repository.py`:
- Line 23: The returned dict from
doc.model_dump(include=set(DomainExecution.__dataclass_fields__)) can contain a
nested Pydantic ResourceUsage instance that won't match the DomainExecution
dataclass type; after creating the data dict from doc.model_dump inside the
function that returns DomainExecution, check if data.get("resource_usage") is
truthy and convert it to the dataclass-compatible type via
ResourceUsageDomain.model_validate(...) (or otherwise reconstruct the nested
field explicitly) before calling DomainExecution(**data), referencing
DomainExecution, ResourceUsageDomain and doc.model_dump to locate where to apply
the fix.

In `@backend/app/domain/saga/models.py`:
- Line 21: The user_id field in SagaContextData currently defaults to a random
UUID which can mask missing propagation; change its default to an empty string
(consistent with other string defaults) so missing user IDs are detectable —
locate the user_id declaration (user_id: str = field(...)) in the
SagaContextData model and replace the default_factory lambda:str(uuid4()) with a
plain empty-string default (or default_factory that returns "") and update any
tests/constructors that assumed an auto-generated UUID.

In `@backend/app/schemas_pydantic/event_schemas.py`:
- Around line 1-11: EventStatistics is duplicated across EventSchemas and
Events; delete the duplicate class definition (EventStatistics) from
event_schemas.py and update any consumers to import the single canonical
EventStatistics from the existing events.py definition (ensure references to
EventStatistics resolve to the class in events.py, e.g., where
admin_user_overview imports it). Also remove the unused type aliases
MongoQueryValue and MongoQuery from event_schemas.py (or if you intend to keep
them, move them into a shared types module and update imports accordingly);
ensure tests/consumers compile after the removal/move.

In `@backend/app/services/user_settings_service.py`:
- Around line 195-201: The _apply_event function is merging
dataclasses.asdict(event) into DomainUserSettings which passes event-only keys
(e.g., event_id, event_type, changed_fields) and triggers a TypeError; fix it by
filtering the event dict to only keys that exist on DomainUserSettings before
merging: compute the allowed keys from dataclasses.asdict(settings) (or
dataclasses.fields(DomainUserSettings)), then build the merge using only {k: v
for k, v in dataclasses.asdict(event).items() if k in allowed_keys and v is not
None}, keep "updated_at": event.timestamp, and construct
DomainUserSettings(**merged) as before.
🧹 Nitpick comments (14)
backend/app/schemas_pydantic/dlq.py (1)

84-96: DLQTopicSummary and DLQTopicSummaryResponse are identical — consolidate them.

Both classes (lines 70–81 and 84–95) have the exact same fields and config. Currently, the repository layer returns DLQTopicSummary (dlq_repository.py:54) and the API route converts it to DLQTopicSummaryResponse (api/routes/dlq.py:99). Eliminate the duplication by using a single class or creating an alias.

Example: Use DLQTopicSummary in the API and optionally alias for backward compatibility
 class DLQTopicSummaryResponse(BaseModel):
-    """Response model for topic summary."""
-
-    model_config = ConfigDict(from_attributes=True)
-
-    topic: str
-    total_messages: int
-    status_breakdown: dict[DLQMessageStatus, int]
-    oldest_message: datetime
-    newest_message: datetime
-    avg_retry_count: float
-    max_retry_count: int
-
-
 class DLQTopicSummary(BaseModel):
     """Summary of a topic in DLQ."""
 
     model_config = ConfigDict(from_attributes=True)
 
     topic: str
     total_messages: int
     status_breakdown: dict[DLQMessageStatus, int]
     oldest_message: datetime
     newest_message: datetime
     avg_retry_count: float
     max_retry_count: int
+
+
+# Alias for backward compatibility with API layer
+DLQTopicSummaryResponse = DLQTopicSummary

Then update api/routes/dlq.py:95–96 and :99 to use DLQTopicSummary directly, removing the unnecessary model_validate call.

backend/app/schemas_pydantic/event_schemas.py (1)

130-140: EventReplayInfoevent_count could drift from len(events).

event_count is declared independently from events: list[DomainEvent]. If callers populate both, they can become inconsistent (e.g., event_count=5 with 3 events in the list). Consider whether event_count should be a computed property or validated against len(events).

backend/app/domain/events/__init__.py (1)

82-97: Domain layer now depends on the schemas layer — layer inversion concern.

The domain package (app.domain.events) now imports from the presentation/schema layer (app.schemas_pydantic.event_schemas). In clean/hexagonal architecture, the domain layer should be the innermost layer with no outward dependencies. This creates a dependency cycle where:

  • app.schemas_pydantic.event_schemas imports DomainEvent, EventMetadata from app.domain.events.typed
  • app.domain.events.__init__ imports schemas back from app.schemas_pydantic.event_schemas

While Python resolves this without a circular import error (since __init__.py imports from .typed, not from itself), it muddies the architectural boundary. Consumers in the domain layer that do from app.domain.events import EventFilter are now silently pulling in a Pydantic schema — which is the opposite direction from the stated goal of separating schemas from domain objects.

Consider either:

  1. Keeping these re-exports for backward compatibility but marking them as deprecated with a migration path, or
  2. Having consumers import directly from app.schemas_pydantic.event_schemas.
#!/bin/bash
# Check which files import these event schema types from the domain package
# to assess the scope of updating imports
echo "=== Imports of event schema types from domain.events ==="
rg -n 'from app\.domain\.events import' --type py | rg -i 'EventFilter|EventListResult|EventBrowseResult|EventDetail|EventSummary|EventStatistics|EventProjection|EventReplayInfo|EventExportRow|ExecutionEventsResult|EventTypeCount|HourlyEventCount|ServiceEventCount|UserEventCount'

echo ""
echo "=== Imports from app.domain.events (wildcard or multi) that might pull schemas ==="
rg -n 'from app\.domain\.events import' --type py -C 2 | head -80
backend/app/domain/saga/models.py (1)

24-45: Saga and SagaInstance share near-identical fields and __post_init__ logic.

Both classes define the same fields (saga_name, execution_id, state, current_step, completed_steps, compensated_steps, context_data, error_message, created_at, updated_at, completed_at, retry_count) and the same __post_init__ dict-to-SagaContextData coercion. Consider extracting a common base or mixin to reduce duplication.

Also applies to: 114-135

backend/app/db/repositories/saga_repository.py (1)

68-68: Consider extracting the repeated set(Saga.__dataclass_fields__) to a module-level constant.

The expression set(Saga.__dataclass_fields__) appears 6 times across this file. Extracting it to a constant (e.g., _SAGA_FIELDS = set(Saga.__dataclass_fields__)) at module level improves readability and reduces repetition.

Suggested refactor

Add at module level (after imports):

_SAGA_FIELDS = set(Saga.__dataclass_fields__)

Then replace each occurrence, e.g.:

-        return Saga(**doc.model_dump(include=set(Saga.__dataclass_fields__))) if doc else None
+        return Saga(**doc.model_dump(include=_SAGA_FIELDS)) if doc else None

Also applies to: 75-75, 79-79, 92-92, 104-104, 129-129

backend/app/services/result_processor/processor.py (1)

66-68: Consider extracting the repeated field-filtering pattern into a helper.

The _fields = ExecutionResultDomain.__dataclass_fields__ / filter / construct pattern is duplicated across all three handlers. A small private method would reduce repetition.

Also applies to: 91-93, 117-121

backend/app/schemas_pydantic/admin_settings.py (1)

5-6: CPU pattern excludes whole-core notation (e.g., "2" or "0.5").

K8S_CPU_PATTERN only accepts millicore format ("500m", "2000m"). Kubernetes also supports decimal notation ("0.5", "2"). If this restriction is intentional for consistency, consider adding a brief comment to document the design choice.

backend/app/db/repositories/saved_script_repository.py (2)

9-11: Consider extracting the repeated doc-to-domain conversion into a helper.

The pattern DomainSavedScript(**doc.model_dump(include=set(DomainSavedScript.__dataclass_fields__))) is repeated at lines 11, 18, 35, and 49. A small helper (e.g., a @staticmethod or module-level function) would reduce duplication and make the field-set computation a one-time cost.

♻️ Suggested helper
+_SAVED_SCRIPT_FIELDS = set(DomainSavedScript.__dataclass_fields__)
+
+def _to_domain(doc: SavedScriptDocument) -> DomainSavedScript:
+    return DomainSavedScript(**doc.model_dump(include=_SAVED_SCRIPT_FIELDS))
+
+
 class SavedScriptRepository:
     async def create_saved_script(self, create_data: DomainSavedScriptCreate, user_id: str) -> DomainSavedScript:
         doc = SavedScriptDocument(**dataclasses.asdict(create_data), user_id=user_id)
         await doc.insert()
-        return DomainSavedScript(**doc.model_dump(include=set(DomainSavedScript.__dataclass_fields__)))
+        return _to_domain(doc)

33-33: Filtering None values also excludes intentionally nullable fields — acceptable here but worth noting.

{k: v for k, v in dataclasses.asdict(update_data).items() if v is not None} treats None as "not set." This is fine for DomainSavedScriptUpdate where all nullable fields use None to mean "no change," but it means you can never explicitly clear a field to None (e.g., setting description to None). If that's a deliberate contract, consider adding a brief docstring or comment on DomainSavedScriptUpdate clarifying this convention.

backend/app/db/repositories/notification_repository.py (1)

24-26: Same doc-to-domain helper opportunity as in SavedScriptRepository.

The DomainNotification(**doc.model_dump(include=set(DomainNotification.__dataclass_fields__))) pattern appears at lines 26, 95, 137, and the subscription variant at lines 168, 184, 194, 206. Consider small helpers to reduce repetition.

backend/app/db/repositories/execution_repository.py (1)

25-25: Repeated set(DomainExecution.__dataclass_fields__) — consider extracting to a module-level constant.

This expression is evaluated on every call. While cheap, extracting it to a module-level constant (e.g., _DOMAIN_EXECUTION_FIELDS = set(DomainExecution.__dataclass_fields__)) improves readability and avoids repeated set construction, especially in get_executions where it runs per-document in a list comprehension.

♻️ Proposed refactor
 from app.domain.execution import (
     DomainExecution,
     DomainExecutionCreate,
     ExecutionResultDomain,
 )
+
+_DOMAIN_EXECUTION_FIELDS = set(DomainExecution.__dataclass_fields__)

Then use _DOMAIN_EXECUTION_FIELDS wherever set(DomainExecution.__dataclass_fields__) appears:

-        return DomainExecution(**doc.model_dump(include=set(DomainExecution.__dataclass_fields__)))
+        return DomainExecution(**doc.model_dump(include=_DOMAIN_EXECUTION_FIELDS))

Also applies to: 35-35, 67-67

backend/app/domain/user/settings_models.py (1)

41-47: Repeated __post_init__ coercion boilerplate for notifications/editor across three classes.

The dict→dataclass coercion for notifications and editor is duplicated in DomainUserSettings, DomainUserSettingsUpdate, and DomainUserSettingsChangedEvent. Consider extracting a small helper to reduce repetition, e.g.:

♻️ Example helper extraction
def _coerce_nested(instance: Any, field_name: str, cls: type) -> None:
    raw = getattr(instance, field_name)
    if isinstance(raw, dict):
        setattr(instance, field_name, cls(**raw))

Then in each __post_init__:

def __post_init__(self) -> None:
    _coerce_nested(self, "notifications", DomainNotificationSettings)
    _coerce_nested(self, "editor", DomainEditorSettings)

Also applies to: 60-66, 87-93

backend/app/db/repositories/user_settings_repository.py (1)

56-59: Long line reduces readability.

Consider extracting the field set to a local variable for clarity, consistent with how _detail_fields is used in admin_events_service.py (line 196).

♻️ Suggested refactor
-        return [
-            DomainUserSettingsChangedEvent(**e.model_dump(include=set(DomainUserSettingsChangedEvent.__dataclass_fields__)))
-            for e in docs
-        ]
+        _fields = set(DomainUserSettingsChangedEvent.__dataclass_fields__)
+        return [
+            DomainUserSettingsChangedEvent(**e.model_dump(include=_fields))
+            for e in docs
+        ]
backend/app/services/user_settings_service.py (1)

82-95: dataclasses.asdict(updates) is computed twice.

Lines 82 and 94 both call dataclasses.asdict(updates) with the same None filter. asdict recursively copies the entire object graph each time. Consider computing it once.

♻️ Suggested refactor
-        changes = {k: v for k, v in dataclasses.asdict(updates).items() if v is not None}
+        updates_dict = dataclasses.asdict(updates)
+        changes = {k: v for k, v in updates_dict.items() if v is not None}
         if not changes:
             return current
 
         ...
 
-        await self._publish_settings_event(
-            user_id, {k: v for k, v in dataclasses.asdict(updates).items() if v is not None}, reason
-        )
+        await self._publish_settings_event(user_id, changes, reason)

if not doc:
return None
return DomainExecution.model_validate(doc)
return DomainExecution(**doc.model_dump(include=set(DomainExecution.__dataclass_fields__)))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check ResourceUsageDomain definition
rg -n 'class ResourceUsageDomain' --type=py -A 15

# Check ResourceUsage definition in docs
rg -n 'class ResourceUsage' --type=py -A 10

# Check if DomainExecution has __post_init__ for resource_usage coercion
rg -n 'class DomainExecution' --type=py -A 25 | head -50

Repository: HardMax71/Integr8sCode

Length of output: 6700


🏁 Script executed:

#!/bin/bash
# Check the full DomainExecution definition with context before line 20
rg -n 'class DomainExecution' --type=py -B 5 -A 30 backend/app/domain/execution/models.py

# Check for __post_init__ in DomainExecution or nearby classes
rg -n '__post_init__' --type=py -B 2 -A 5 backend/app/domain/execution/models.py

Repository: HardMax71/Integr8sCode

Length of output: 1981


🏁 Script executed:

#!/bin/bash
# View the sse_repository.py file around line 23
sed -n '1,35p' backend/app/db/repositories/sse_repository.py

# Also check the imports to understand the types
head -30 backend/app/db/repositories/sse_repository.py

Repository: HardMax71/Integr8sCode

Length of output: 1931


🏁 Script executed:

#!/bin/bash
# Check ExecutionDocument definition to see resource_usage type
sed -n '13,40p' backend/app/db/docs/execution.py

# Also check if there's any conversion logic elsewhere
rg -n 'ResourceUsageDomain' --type=py -B 2 -A 2 backend/app/db/repositories/sse_repository.py

Repository: HardMax71/Integr8sCode

Length of output: 1140


Fix type mismatch: Convert nested resource_usage Pydantic model to ResourceUsageDomain.

When model_dump(include=...) serializes nested models with the default mode="python", it returns resource_usage as a ResourceUsage instance—not a dict. Unpacking this directly into the DomainExecution dataclass (which expects ResourceUsageDomain | None) will fail at runtime when resource_usage is not None, since dataclasses do not coerce types.

Use ResourceUsageDomain.model_validate(doc.resource_usage) to convert, or reconstruct the nested field explicitly after unpacking:

data = doc.model_dump(include=set(DomainExecution.__dataclass_fields__))
if data.get("resource_usage"):
    data["resource_usage"] = ResourceUsageDomain.model_validate(data["resource_usage"])
return DomainExecution(**data)
🤖 Prompt for AI Agents
In `@backend/app/db/repositories/sse_repository.py` at line 23, The returned dict
from doc.model_dump(include=set(DomainExecution.__dataclass_fields__)) can
contain a nested Pydantic ResourceUsage instance that won't match the
DomainExecution dataclass type; after creating the data dict from doc.model_dump
inside the function that returns DomainExecution, check if
data.get("resource_usage") is truthy and convert it to the dataclass-compatible
type via ResourceUsageDomain.model_validate(...) (or otherwise reconstruct the
nested field explicitly) before calling DomainExecution(**data), referencing
DomainExecution, ResourceUsageDomain and doc.model_dump to locate where to apply
the fix.

Comment on lines 195 to +201
def _apply_event(self, settings: DomainUserSettings, event: DomainUserSettingsChangedEvent) -> DomainUserSettings:
"""Apply a settings update event via dict merge + model_validate."""
return DomainUserSettings.model_validate(
{**settings.model_dump(), **event.model_dump(exclude_none=True), "updated_at": event.timestamp}
)
"""Apply a settings update event via dict merge."""
return DomainUserSettings(**{
**dataclasses.asdict(settings),
**{k: v for k, v in dataclasses.asdict(event).items() if v is not None},
"updated_at": event.timestamp,
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: _apply_event passes event-only fields to DomainUserSettings, causing TypeError.

dataclasses.asdict(event) includes keys like event_id, event_type, timestamp, changed_fields, and reason that do not exist on DomainUserSettings. The v is not None filter keeps required/non-None event fields (e.g., event_id, event_type, changed_fields). The DomainUserSettings(**merged) constructor will reject these unknown keyword arguments with a TypeError.

The previous Pydantic model_validate silently ignored extra fields, but dataclass constructors are strict.

🐛 Proposed fix — restrict merge to settings-applicable fields
     def _apply_event(self, settings: DomainUserSettings, event: DomainUserSettingsChangedEvent) -> DomainUserSettings:
         """Apply a settings update event via dict merge."""
+        settings_fields = set(DomainUserSettings.__dataclass_fields__)
+        event_updates = {
+            k: v for k, v in dataclasses.asdict(event).items()
+            if v is not None and k in settings_fields
+        }
         return DomainUserSettings(**{
             **dataclasses.asdict(settings),
-            **{k: v for k, v in dataclasses.asdict(event).items() if v is not None},
+            **event_updates,
             "updated_at": event.timestamp,
         })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _apply_event(self, settings: DomainUserSettings, event: DomainUserSettingsChangedEvent) -> DomainUserSettings:
"""Apply a settings update event via dict merge + model_validate."""
return DomainUserSettings.model_validate(
{**settings.model_dump(), **event.model_dump(exclude_none=True), "updated_at": event.timestamp}
)
"""Apply a settings update event via dict merge."""
return DomainUserSettings(**{
**dataclasses.asdict(settings),
**{k: v for k, v in dataclasses.asdict(event).items() if v is not None},
"updated_at": event.timestamp,
})
def _apply_event(self, settings: DomainUserSettings, event: DomainUserSettingsChangedEvent) -> DomainUserSettings:
"""Apply a settings update event via dict merge."""
settings_fields = set(DomainUserSettings.__dataclass_fields__)
event_updates = {
k: v for k, v in dataclasses.asdict(event).items()
if v is not None and k in settings_fields
}
return DomainUserSettings(**{
**dataclasses.asdict(settings),
**event_updates,
"updated_at": event.timestamp,
})
🤖 Prompt for AI Agents
In `@backend/app/services/user_settings_service.py` around lines 195 - 201, The
_apply_event function is merging dataclasses.asdict(event) into
DomainUserSettings which passes event-only keys (e.g., event_id, event_type,
changed_fields) and triggers a TypeError; fix it by filtering the event dict to
only keys that exist on DomainUserSettings before merging: compute the allowed
keys from dataclasses.asdict(settings) (or
dataclasses.fields(DomainUserSettings)), then build the merge using only {k: v
for k, v in dataclasses.asdict(event).items() if k in allowed_keys and v is not
None}, keep "updated_at": event.timestamp, and construct
DomainUserSettings(**merged) as before.

@sonarqubecloud
Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@backend/app/schemas_pydantic/event_schemas.py`:
- Around line 21-33: There are two conflicting EventFilter class definitions
(one in EventFilter and a local one in admin_events) causing masking via
model_validate and an unused status field; fix by consolidating: either remove
the local EventFilter class in admin_events and import the canonical EventFilter
(update imports/usages and remove any model_validate conversions), or if they
serve different purposes rename the local class to a distinct name (e.g.,
EventFilterRequest) and remove the unused status field from whichever class
doesn’t need it; ensure all routes/controllers reference the single chosen
symbol (EventFilter or the new name) and update imports accordingly.

In `@backend/app/schemas_pydantic/replay_schemas.py`:
- Around line 86-88: The field errors: list[ReplayError] uses ReplayError which
is currently a stdlib dataclass and causes Pydantic serialization failures
(e.g., when calling ReplaySession.model_dump() or FastAPI response
serialization); replace the stdlib dataclass with a Pydantic dataclass by
importing and decorating ReplayError with pydantic.dataclasses.dataclass
(maintain the existing datetime-typed field), so that ReplayError becomes
Pydantic-aware and the errors list serializes correctly.
🧹 Nitpick comments (4)
backend/app/schemas_pydantic/event_schemas.py (1)

36-56: EventListResult and EventBrowseResult are near-identical — consider unifying.

Both models wrap list[DomainEvent] with total, skip, and limit. The only difference is EventListResult adds has_more: bool. Consider making one model with an optional has_more field, or having EventListResult extend EventBrowseResult.

♻️ Example consolidation
-class EventListResult(BaseModel):
-    """Result of event list query."""
-    model_config = ConfigDict(from_attributes=True)
-    events: list[DomainEvent]
-    total: int
-    skip: int
-    limit: int
-    has_more: bool
-
-
-class EventBrowseResult(BaseModel):
-    """Result for event browsing."""
-    model_config = ConfigDict(from_attributes=True)
-    events: list[DomainEvent]
-    total: int
-    skip: int
-    limit: int
+class EventBrowseResult(BaseModel):
+    """Result for event browsing."""
+    model_config = ConfigDict(from_attributes=True)
+    events: list[DomainEvent]
+    total: int
+    skip: int
+    limit: int
+
+
+class EventListResult(EventBrowseResult):
+    """Result of event list query (adds pagination flag)."""
+    has_more: bool
backend/app/domain/admin/settings_models.py (1)

7-19: Validation rules are duplicated between domain and schema layers.

K8S_MEMORY_PATTERN, K8S_CPU_PATTERN, and the range bounds in _RANGE_RULES are defined here and again (as raw strings and ge/le constraints) in backend/app/schemas_pydantic/admin_settings.py. If a bound changes in one place but not the other, requests could pass API validation but fail domain construction (or vice versa).

Consider having the schema import and reference the domain-level constants (e.g., _RANGE_RULES and the compiled patterns) as the single source of truth, or extract them into a shared constants module.

backend/app/schemas_pydantic/replay_schemas.py (1)

91-107: ReplayRequest embeds the domain ReplayFilter directly — consider using ReplayFilterSchema instead.

ReplayFilter is a domain dataclass with methods like to_mongo_query() and is_empty(). Using it directly in a Pydantic request schema couples the API surface to domain internals. If domain fields are added to ReplayFilter (e.g., event_ids, aggregate_id are already present on the domain model but absent from ReplayFilterSchema), they'd become silently accepted API input.

ReplayFilterSchema already exists and defines the intended API surface — consider using it here instead.

Proposed fix
 class ReplayRequest(BaseModel):
     """Request schema for creating replay sessions"""

     replay_type: ReplayType
     target: ReplayTarget = ReplayTarget.KAFKA
-    filter: ReplayFilter = Field(default_factory=ReplayFilter)
+    filter: ReplayFilterSchema = Field(default_factory=ReplayFilterSchema)
backend/app/domain/admin/__init__.py (1)

1-2: Layer dependency: domain package imports from schemas_pydantic.

ExecutionResultSummary is now sourced from app.schemas_pydantic.replay_schemas and re-exported from the domain admin package. This introduces a dependency from the domain layer to the schema/presentation layer, which inverts the typical dependency direction (schemas should depend on domain, not vice versa). The public API surface is preserved, so this isn't a breaking change, but it may be worth noting for future architectural cleanups.

Comment on lines +21 to +33
class EventFilter(BaseModel):
"""Filter criteria for querying events."""

model_config = ConfigDict(from_attributes=True)

event_types: list[EventType] | None = None
aggregate_id: str | None = None
user_id: str | None = None
service_name: str | None = None
start_time: datetime | None = None
end_time: datetime | None = None
search_text: str | None = None
status: str | None = None
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all definitions of EventFilter
echo "=== EventFilter class definitions ==="
rg -n 'class EventFilter' --type py -A 2

echo ""
echo "=== EventFilter imports and usage ==="
rg -n 'EventFilter' --type py -B 1 -A 1 | head -100

Repository: HardMax71/Integr8sCode

Length of output: 6631


🏁 Script executed:

#!/bin/bash
# Check what EventFilter is exported from domain/events
echo "=== backend/app/domain/events/__init__.py content (around EventFilter) ==="
cat -n backend/app/domain/events/__init__.py | grep -A 30 "from.*import"

echo ""
echo "=== EventFilter fields in event_schemas.py ==="
sed -n '21,33p' backend/app/schemas_pydantic/event_schemas.py

echo ""
echo "=== EventFilter fields in admin_events.py ==="
sed -n '12,25p' backend/app/schemas_pydantic/admin_events.py

Repository: HardMax71/Integr8sCode

Length of output: 2999


🏁 Script executed:

#!/bin/bash
# Check where admin_events_service.py imports EventFilter from
echo "=== admin_events_service.py imports ==="
head -30 backend/app/services/admin/admin_events_service.py | grep -A 10 "^from\|^import"

echo ""
echo "=== admin_events.py full import/export context ==="
head -50 backend/app/schemas_pydantic/admin_events.py

echo ""
echo "=== Check where EventBrowseRequest is used ==="
rg -n 'EventBrowseRequest' --type py -B 2 -A 2

Repository: HardMax71/Integr8sCode

Length of output: 6367


🏁 Script executed:

#!/bin/bash
# Check what happens in the conversion - look at the exact line in the route
echo "=== API route conversion logic ==="
sed -n '30,45p' backend/app/api/routes/admin/events.py

echo ""
echo "=== Check if repository/service actually need status field ==="
rg -n 'status' backend/app/db/repositories/admin/admin_events_repository.py -B 2 -A 2

Repository: HardMax71/Integr8sCode

Length of output: 942


🏁 Script executed:

#!/bin/bash
# Check if status field in EventFilter is actually used in filtering logic
echo "=== Repository filter conditions method ==="
sed -n '33,46p' backend/app/db/repositories/admin/admin_events_repository.py

echo ""
echo "=== Check if status is referenced in filter building ==="
rg -n '_event_filter_conditions|f\.status' backend/app/db/repositories/admin/admin_events_repository.py -A 15

echo ""
echo "=== Check if status field is used in any export/list methods ==="
rg -n 'event_filter' backend/app/db/repositories/admin/admin_events_repository.py -B 2 -A 10 | head -80

Repository: HardMax71/Integr8sCode

Length of output: 5567


🏁 Script executed:

#!/bin/bash
# Final check: is the status field used ANYWHERE in the codebase?
echo "=== Search for status field usage in EventFilter context ==="
rg -n '\.status' backend/app/schemas_pydantic/event_schemas.py backend/app/services/admin/ backend/app/db/repositories/admin/ --type py

echo ""
echo "=== Verify which EventFilter is the canonical one ==="
echo "Imports of EventFilter in test file:"
grep -n "from.*EventFilter\|import.*EventFilter" backend/tests/e2e/test_admin_events_routes.py

echo ""
echo "Imports of EventFilter in API routes:"
grep -n "from.*EventFilter\|import.*EventFilter" backend/app/api/routes/admin/events.py

Repository: HardMax71/Integr8sCode

Length of output: 519


Remove duplicate EventFilter class or consolidate into a single definition.

Two EventFilter classes exist in the same package with conflicting field definitions:

  • event_schemas.py (exported via domain.events): includes unused status field
  • admin_events.py: omits status field

The API route converts between them with model_validate(), masking a design inconsistency. The status field is never used in filtering logic. Choose one:

  1. Keep only the event_schemas.py version and remove the local definition from admin_events.py, or
  2. Give them distinct names if they genuinely serve different purposes (e.g., EventFilterRequest, EventFilterQuery)
🤖 Prompt for AI Agents
In `@backend/app/schemas_pydantic/event_schemas.py` around lines 21 - 33, There
are two conflicting EventFilter class definitions (one in EventFilter and a
local one in admin_events) causing masking via model_validate and an unused
status field; fix by consolidating: either remove the local EventFilter class in
admin_events and import the canonical EventFilter (update imports/usages and
remove any model_validate conversions), or if they serve different purposes
rename the local class to a distinct name (e.g., EventFilterRequest) and remove
the unused status field from whichever class doesn’t need it; ensure all
routes/controllers reference the single chosen symbol (EventFilter or the new
name) and update imports accordingly.

Comment on lines +86 to +88
last_event_at: datetime | None = None

errors: list[ReplayError] = Field(default_factory=list)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find ReplayError definition
ast-grep --pattern $'@dataclass
class ReplayError:
  $$$'

Repository: HardMax71/Integr8sCode

Length of output: 1330


🏁 Script executed:

# Also search for ReplayError in the file to understand context
rg -n "ReplayError" backend/app/schemas_pydantic/replay_schemas.py -B 2 -A 5

Repository: HardMax71/Integr8sCode

Length of output: 526


🏁 Script executed:

# Check ReplaySession configuration
rg -n "class ReplaySession" backend/app/schemas_pydantic/replay_schemas.py -A 30

Repository: HardMax71/Integr8sCode

Length of output: 1213


🏁 Script executed:

# Search for ReplaySession usage in endpoints/routes
fd -e py | xargs rg -l "ReplaySession" | grep -E "(route|endpoint|api|handler)" | head -10

Repository: HardMax71/Integr8sCode

Length of output: 138


🏁 Script executed:

# Search for ReplaySession usage more broadly
rg "ReplaySession" -A 3 | grep -E "(return|response|model_dump|json)" | head -20

Repository: HardMax71/Integr8sCode

Length of output: 1545


🏁 Script executed:

# Check for tests related to ReplaySession serialization
fd -e py -path "*/test*" | xargs rg -l "ReplaySession" 2>/dev/null

Repository: HardMax71/Integr8sCode

Length of output: 1280


🏁 Script executed:

# Check if there are any JSON schema or serialization related code
rg "model_dump|model_validate|from_attributes" backend/app/schemas_pydantic/replay_schemas.py -B 2 -A 2

Repository: HardMax71/Integr8sCode

Length of output: 1136


🏁 Script executed:

# Check the API routes that return ReplaySession
rg -n "ReplaySession" backend/app/api/routes/replay.py -B 3 -A 10

Repository: HardMax71/Integr8sCode

Length of output: 1426


🏁 Script executed:

# Check the e2e test to see what it's testing
cat -n backend/tests/e2e/test_replay_routes.py | head -80

Repository: HardMax71/Integr8sCode

Length of output: 3227


🏁 Script executed:

# Search for any serialization issues or workarounds in the codebase
rg "ReplayError" backend/app -A 3 -B 1 | grep -E "(serializ|dump|json|validation)" | head -20

Repository: HardMax71/Integr8sCode

Length of output: 48


🏁 Script executed:

# Check the pydantic-dataclasses documentation referenced in the findings
cat -n docs/architecture/pydantic-dataclasses.md

Repository: HardMax71/Integr8sCode

Length of output: 8993


🏁 Script executed:

# Check the ReplayError import and full definition in domain/replay/models.py
cat -n backend/app/domain/replay/models.py

Repository: HardMax71/Integr8sCode

Length of output: 6296


🏁 Script executed:

# Check if ReplaySession is properly handling ReplayError in JSON serialization by looking at actual responses
rg "model_dump|model_dump_json|json()" backend/app/api/routes/replay.py -B 2 -A 2

Repository: HardMax71/Integr8sCode

Length of output: 360


ReplayError must be a Pydantic dataclass to support Pydantic serialization.

ReplayError is defined as a stdlib dataclass with a datetime field. When ReplaySession.model_dump() or FastAPI serializes the response, Pydantic cannot handle stdlib dataclasses with complex types like datetime. Convert ReplayError to a Pydantic dataclass (consistent with the codebase pattern in pydantic-dataclasses.md):

from pydantic.dataclasses import dataclass
🤖 Prompt for AI Agents
In `@backend/app/schemas_pydantic/replay_schemas.py` around lines 86 - 88, The
field errors: list[ReplayError] uses ReplayError which is currently a stdlib
dataclass and causes Pydantic serialization failures (e.g., when calling
ReplaySession.model_dump() or FastAPI response serialization); replace the
stdlib dataclass with a Pydantic dataclass by importing and decorating
ReplayError with pydantic.dataclasses.dataclass (maintain the existing
datetime-typed field), so that ReplayError becomes Pydantic-aware and the errors
list serializes correctly.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 21 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/app/schemas_pydantic/replay_schemas.py">

<violation number="1" location="backend/app/schemas_pydantic/replay_schemas.py:40">
P2: ReplayFilterSchema omits event_ids and aggregate_id, so replay session responses drop those filter values even though the domain ReplayFilter accepts them. This changes the API payload and can hide filters that clients sent.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

class ReplayFilterSchema(BaseModel):
model_config = ConfigDict(from_attributes=True)

execution_id: str | None = None
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: ReplayFilterSchema omits event_ids and aggregate_id, so replay session responses drop those filter values even though the domain ReplayFilter accepts them. This changes the API payload and can hide filters that clients sent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/app/schemas_pydantic/replay_schemas.py, line 40:

<comment>ReplayFilterSchema omits event_ids and aggregate_id, so replay session responses drop those filter values even though the domain ReplayFilter accepts them. This changes the API payload and can hide filters that clients sent.</comment>

<file context>
@@ -22,3 +32,128 @@ class ExecutionResultSummary(BaseModel):
+class ReplayFilterSchema(BaseModel):
+    model_config = ConfigDict(from_attributes=True)
+
+    execution_id: str | None = None
+    event_types: list[EventType] | None = None
+    start_time: datetime | None = None
</file context>
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments