55used for project generation and validation.
66"""
77
8- from dataclasses import dataclass
8+ from dataclasses import dataclass , field
99from enum import Enum
1010
11+ from .file_manifest import FileManifest
12+
1113
1214class ComponentType (Enum ):
1315 """Component type classifications."""
@@ -35,27 +37,17 @@ class ComponentSpec:
3537 name : str
3638 type : ComponentType
3739 description : str
38- requires : list [str ] | None = None # Hard dependencies
39- recommends : list [str ] | None = None # Soft dependencies
40- conflicts : list [str ] | None = None # Mutual exclusions
41- docker_services : list [str ] | None = None
42- pyproject_deps : list [str ] | None = None
43- template_files : list [str ] | None = None
44-
45- def __post_init__ (self ) -> None :
46- """Ensure all list fields are initialized."""
47- if self .requires is None :
48- self .requires = []
49- if self .recommends is None :
50- self .recommends = []
51- if self .conflicts is None :
52- self .conflicts = []
53- if self .docker_services is None :
54- self .docker_services = []
55- if self .pyproject_deps is None :
56- self .pyproject_deps = []
57- if self .template_files is None :
58- self .template_files = []
40+ requires : list [str ] = field (default_factory = list ) # Hard dependencies
41+ recommends : list [str ] = field (default_factory = list ) # Soft dependencies
42+ conflicts : list [str ] = field (default_factory = list ) # Mutual exclusions
43+ docker_services : list [str ] = field (default_factory = list )
44+ pyproject_deps : list [str ] = field (default_factory = list )
45+ template_files : list [str ] = field (default_factory = list )
46+ # R1 file manifest used by cleanup_components(). The legacy
47+ # post_gen_tasks.get_component_file_mapping() dict is still maintained
48+ # separately, so this manifest must be kept aligned with it by hand
49+ # until R2 derives the mapping from manifests. See file_manifest.py.
50+ files : FileManifest = field (default_factory = FileManifest )
5951
6052
6153# Component registry - single source of truth
@@ -66,20 +58,28 @@ def __post_init__(self) -> None:
6658 description = "FastAPI backend server" ,
6759 pyproject_deps = ["fastapi==0.116.1" , "uvicorn==0.35.0" ],
6860 template_files = ["app/components/backend/" ],
61+ # backend is a CORE component; never cleaned up.
6962 ),
7063 "frontend" : ComponentSpec (
7164 name = "frontend" ,
7265 type = ComponentType .CORE ,
7366 description = "Flet frontend interface" ,
7467 pyproject_deps = ["flet==0.28.3" ],
7568 template_files = ["app/components/frontend/" ],
69+ # frontend is a CORE component; never cleaned up.
7670 ),
7771 "redis" : ComponentSpec (
7872 name = "redis" ,
7973 type = ComponentType .INFRASTRUCTURE ,
8074 description = "Redis cache and message broker" ,
8175 docker_services = ["redis" ],
8276 pyproject_deps = ["redis==5.0.8" ],
77+ files = FileManifest (
78+ primary = [
79+ "app/components/frontend/dashboard/cards/redis_card.py" ,
80+ "app/components/frontend/dashboard/modals/redis_modal.py" ,
81+ ],
82+ ),
8383 ),
8484 "worker" : ComponentSpec (
8585 name = "worker" ,
@@ -89,6 +89,26 @@ def __post_init__(self) -> None:
8989 pyproject_deps = ["arq==0.25.0" ],
9090 docker_services = ["worker-system" , "worker-load-test" ],
9191 template_files = ["app/components/worker/" ],
92+ files = FileManifest (
93+ # Mirrors cleanup_components() lines 316-333 (worker NOT enabled).
94+ # task_history_section.py is intentionally NOT here — cleanup
95+ # leaves it. worker_taskiq.py IS here — cleanup removes it.
96+ primary = [
97+ "app/components/worker" ,
98+ "app/cli/load_test.py" ,
99+ "app/services/load_test.py" ,
100+ "app/services/load_test_models.py" ,
101+ "app/services/load_test_workloads.py" ,
102+ "tests/services/test_load_test_models.py" ,
103+ "tests/services/test_load_test_service.py" ,
104+ "tests/services/test_worker_health_registration.py" ,
105+ "app/components/backend/api/worker.py" ,
106+ "app/components/backend/api/worker_taskiq.py" ,
107+ "tests/api/test_worker_endpoints.py" ,
108+ "app/components/frontend/dashboard/cards/worker_card.py" ,
109+ "app/components/frontend/dashboard/modals/worker_modal.py" ,
110+ ],
111+ ),
92112 ),
93113 "scheduler" : ComponentSpec (
94114 name = "scheduler" ,
@@ -97,6 +117,22 @@ def __post_init__(self) -> None:
97117 pyproject_deps = ["apscheduler==3.10.4" ],
98118 docker_services = ["scheduler" ],
99119 template_files = ["app/components/scheduler.py" , "app/entrypoints/scheduler.py" ],
120+ files = FileManifest (
121+ primary = [
122+ "app/entrypoints/scheduler.py" ,
123+ "app/components/scheduler" ,
124+ "tests/components/test_scheduler.py" ,
125+ "docs/components/scheduler.md" ,
126+ "app/components/backend/api/scheduler.py" ,
127+ "tests/api/test_scheduler_endpoints.py" ,
128+ "app/components/frontend/dashboard/cards/scheduler_card.py" ,
129+ "app/components/frontend/dashboard/modals/scheduler_modal.py" ,
130+ "tests/services/test_scheduled_task_manager.py" ,
131+ ],
132+ # scheduler persistence cleanup is option-driven
133+ # (scheduler_backend == MEMORY), not a simple AnswerKey toggle —
134+ # it stays inline in cleanup_components() for R1.
135+ ),
100136 ),
101137 "database" : ComponentSpec (
102138 name = "database" ,
@@ -105,20 +141,41 @@ def __post_init__(self) -> None:
105141 pyproject_deps = ["sqlmodel>=0.0.14" , "sqlalchemy>=2.0.0" ],
106142 # Note: async driver (aiosqlite or asyncpg) selected based on database_type in copier.yml
107143 template_files = ["app/core/db.py" ],
144+ files = FileManifest (
145+ primary = [
146+ "app/core/db.py" ,
147+ "app/components/frontend/dashboard/cards/database_card.py" ,
148+ "app/components/frontend/dashboard/modals/database_modal.py" ,
149+ ],
150+ ),
108151 ),
109152 "ingress" : ComponentSpec (
110153 name = "ingress" ,
111154 type = ComponentType .INFRASTRUCTURE ,
112155 description = "Traefik reverse proxy and load balancer" ,
113156 docker_services = ["traefik" ],
114157 recommends = ["backend" ],
158+ files = FileManifest (
159+ primary = [
160+ "traefik" ,
161+ "app/components/frontend/dashboard/cards/ingress_card.py" ,
162+ "app/components/frontend/dashboard/modals/ingress_modal.py" ,
163+ ],
164+ ),
115165 ),
116166 "observability" : ComponentSpec (
117167 name = "observability" ,
118168 type = ComponentType .INFRASTRUCTURE ,
119169 description = "Logfire observability, tracing, and metrics" ,
120170 pyproject_deps = ["logfire[fastapi,httpx]" ],
121171 template_files = ["app/components/backend/middleware/logfire_tracing.py" ],
172+ files = FileManifest (
173+ primary = [
174+ "app/components/backend/middleware/logfire_tracing.py" ,
175+ "app/components/frontend/dashboard/cards/observability_card.py" ,
176+ "app/components/frontend/dashboard/modals/observability_modal.py" ,
177+ ],
178+ ),
122179 ),
123180}
124181
0 commit comments