aptitude-resolver 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. aptitude/__init__.py +7 -0
  2. aptitude/application/__init__.py +1 -0
  3. aptitude/application/composition.py +341 -0
  4. aptitude/application/dto/__init__.py +59 -0
  5. aptitude/application/dto/install_dto.py +88 -0
  6. aptitude/application/dto/resolve_request_dto.py +20 -0
  7. aptitude/application/dto/resolve_result_dto.py +263 -0
  8. aptitude/application/queries/__init__.py +13 -0
  9. aptitude/application/queries/plan_skill_resolution.py +370 -0
  10. aptitude/application/use_cases/__init__.py +13 -0
  11. aptitude/application/use_cases/install_skill.py +139 -0
  12. aptitude/application/use_cases/resolution_mapping.py +259 -0
  13. aptitude/application/use_cases/resolve_skill_query.py +86 -0
  14. aptitude/application/use_cases/sync_from_lock.py +100 -0
  15. aptitude/cache/__init__.py +20 -0
  16. aptitude/cache/keys.py +34 -0
  17. aptitude/cache/store.py +61 -0
  18. aptitude/discovery/__init__.py +13 -0
  19. aptitude/discovery/candidate_discovery.py +155 -0
  20. aptitude/discovery/intent/__init__.py +8 -0
  21. aptitude/discovery/intent/parsing.py +63 -0
  22. aptitude/discovery/query_builder/__init__.py +5 -0
  23. aptitude/discovery/query_builder/build_query.py +17 -0
  24. aptitude/discovery/reranking/__init__.py +5 -0
  25. aptitude/discovery/reranking/candidate_reranker.py +584 -0
  26. aptitude/domain/__init__.py +1 -0
  27. aptitude/domain/errors/__init__.py +41 -0
  28. aptitude/domain/errors/resolver_errors.py +188 -0
  29. aptitude/domain/models/__init__.py +33 -0
  30. aptitude/domain/models/dependency_spec.py +16 -0
  31. aptitude/domain/models/discovered_skill.py +15 -0
  32. aptitude/domain/models/discovery_candidate.py +26 -0
  33. aptitude/domain/models/discovery_query.py +16 -0
  34. aptitude/domain/models/resolution_graph.py +59 -0
  35. aptitude/domain/models/search_intent.py +18 -0
  36. aptitude/domain/models/skill_coordinate.py +13 -0
  37. aptitude/domain/models/skill_identity.py +21 -0
  38. aptitude/domain/models/skill_metadata.py +31 -0
  39. aptitude/domain/models/version_summary.py +29 -0
  40. aptitude/domain/policy/__init__.py +26 -0
  41. aptitude/domain/policy/models.py +97 -0
  42. aptitude/domain/policy/ranking.py +33 -0
  43. aptitude/domain/policy/selection.py +40 -0
  44. aptitude/domain/tracing/__init__.py +5 -0
  45. aptitude/domain/tracing/models.py +15 -0
  46. aptitude/execution/__init__.py +29 -0
  47. aptitude/execution/debug_artifacts.py +107 -0
  48. aptitude/execution/materialize.py +174 -0
  49. aptitude/execution/plan.py +67 -0
  50. aptitude/governance/__init__.py +8 -0
  51. aptitude/governance/evaluator.py +304 -0
  52. aptitude/interfaces/__init__.py +1 -0
  53. aptitude/interfaces/cli/__init__.py +5 -0
  54. aptitude/interfaces/cli/app.py +730 -0
  55. aptitude/interfaces/cli/catalog.py +500 -0
  56. aptitude/interfaces/cli/main.py +21 -0
  57. aptitude/interfaces/cli/support.py +389 -0
  58. aptitude/interfaces/cli/wizard.py +1101 -0
  59. aptitude/interfaces/shared/__init__.py +9 -0
  60. aptitude/interfaces/shared/install_workflow.py +233 -0
  61. aptitude/lockfile/__init__.py +35 -0
  62. aptitude/lockfile/model.py +97 -0
  63. aptitude/lockfile/parser.py +247 -0
  64. aptitude/lockfile/replay.py +91 -0
  65. aptitude/lockfile/serializer.py +247 -0
  66. aptitude/registry/__init__.py +5 -0
  67. aptitude/registry/client.py +344 -0
  68. aptitude/registry/mappers.py +108 -0
  69. aptitude/registry/transport_models.py +125 -0
  70. aptitude/resolution/__init__.py +1 -0
  71. aptitude/resolution/conflict/__init__.py +7 -0
  72. aptitude/resolution/conflict/conflict_rules.py +20 -0
  73. aptitude/resolution/graph/__init__.py +7 -0
  74. aptitude/resolution/graph/recursive_graph_resolver.py +246 -0
  75. aptitude/resolution/normalizer/__init__.py +7 -0
  76. aptitude/resolution/normalizer/dependency_normalizer.py +22 -0
  77. aptitude/resolution/solver/__init__.py +19 -0
  78. aptitude/resolution/solver/candidate_selection.py +92 -0
  79. aptitude/resolution/solver/candidate_version_resolution.py +228 -0
  80. aptitude/resolution/solver/version_selection.py +41 -0
  81. aptitude/resolution/validation/__init__.py +7 -0
  82. aptitude/resolution/validation/graph_validator.py +18 -0
  83. aptitude/shared/__init__.py +1 -0
  84. aptitude/shared/config/__init__.py +31 -0
  85. aptitude/shared/config/aptitude_config.py +133 -0
  86. aptitude/shared/config/settings.py +87 -0
  87. aptitude/shared/logging/__init__.py +5 -0
  88. aptitude/shared/logging/configure.py +27 -0
  89. aptitude/telemetry/__init__.py +9 -0
  90. aptitude/telemetry/metrics.py +61 -0
  91. aptitude_resolver-0.0.1.dist-info/METADATA +368 -0
  92. aptitude_resolver-0.0.1.dist-info/RECORD +94 -0
  93. aptitude_resolver-0.0.1.dist-info/WHEEL +4 -0
  94. aptitude_resolver-0.0.1.dist-info/entry_points.txt +3 -0
aptitude/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ """Aptitude package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __version__ = "0.0.1"
6
+
7
+ __all__ = ["__version__"]
@@ -0,0 +1 @@
1
+ """Application layer package."""
@@ -0,0 +1,341 @@
1
+ """Application-owned wiring helpers for configured use cases."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from pathlib import Path
7
+
8
+ from pydantic import ValidationError
9
+
10
+ from aptitude.application.use_cases import (
11
+ InstallSkillUseCase,
12
+ ResolveSkillQueryUseCase,
13
+ SyncFromLockUseCase,
14
+ )
15
+ from aptitude.domain.errors import InvalidResolverConfigurationError
16
+ from aptitude.domain.policy import PolicyContext, SelectionPreferences
17
+ from aptitude.registry import RegistryClient
18
+ from aptitude.shared.config import (
19
+ AptitudeConfig,
20
+ PolicyConfig,
21
+ SelectionConfig,
22
+ Settings,
23
+ describe_settings_validation_error,
24
+ load_user_aptitude_config,
25
+ load_workspace_aptitude_config,
26
+ read_env_selection_overrides,
27
+ )
28
+
29
+
30
+ def build_registry_client() -> tuple[RegistryClient, Callable[[], None]]:
31
+ """Create a registry client and its cleanup hook."""
32
+
33
+ try:
34
+ settings = Settings()
35
+ except ValidationError as exc:
36
+ raise InvalidResolverConfigurationError(
37
+ "environment", describe_settings_validation_error(exc)
38
+ ) from exc
39
+
40
+ registry_client = RegistryClient(settings)
41
+ return registry_client, registry_client.close
42
+
43
+
44
+ def _effective_selection_preferences(
45
+ *,
46
+ selection_profile_override: str | None = None,
47
+ interaction_mode_override: str | None = None,
48
+ cwd: Path | None = None,
49
+ ) -> SelectionPreferences:
50
+ """Build one effective selection-preference object from all current sources."""
51
+
52
+ default_preferences = SelectionPreferences()
53
+ sources = [
54
+ (
55
+ "user_config",
56
+ "user config",
57
+ _selection_config(load_user_aptitude_config, "user config"),
58
+ ),
59
+ (
60
+ "workspace_config",
61
+ "workspace config",
62
+ _selection_config(
63
+ lambda: load_workspace_aptitude_config(cwd),
64
+ "workspace config",
65
+ ),
66
+ ),
67
+ ("environment", "environment", read_env_selection_overrides()),
68
+ (
69
+ "cli_override",
70
+ "CLI override",
71
+ SelectionConfig(
72
+ profile=selection_profile_override,
73
+ interaction_mode=interaction_mode_override,
74
+ )
75
+ if selection_profile_override is not None
76
+ or interaction_mode_override is not None
77
+ else None,
78
+ ),
79
+ ]
80
+
81
+ effective_profile = default_preferences.profile
82
+ effective_profile_source = "default"
83
+ effective_profile_error_source = "default"
84
+ effective_interaction_mode = default_preferences.interaction_mode
85
+ effective_interaction_source = "default"
86
+ effective_interaction_error_source = "default"
87
+
88
+ for source_id, _source_name, selection_config in sources:
89
+ if selection_config is None:
90
+ continue
91
+ if selection_config.profile is not None:
92
+ effective_profile = selection_config.profile
93
+ effective_profile_source = source_id
94
+ effective_profile_error_source = _source_name
95
+ if selection_config.interaction_mode is not None:
96
+ effective_interaction_mode = selection_config.interaction_mode
97
+ effective_interaction_source = source_id
98
+ effective_interaction_error_source = _source_name
99
+
100
+ try:
101
+ return SelectionPreferences(
102
+ profile=effective_profile,
103
+ interaction_mode=effective_interaction_mode,
104
+ profile_source=effective_profile_source,
105
+ interaction_mode_source=effective_interaction_source,
106
+ )
107
+ except ValueError as exc:
108
+ source = (
109
+ effective_profile_error_source
110
+ if "profile" in str(exc).lower()
111
+ else effective_interaction_error_source
112
+ )
113
+ raise InvalidResolverConfigurationError(source, str(exc)) from exc
114
+
115
+
116
+ def _effective_policy_context(
117
+ *,
118
+ allowed_trust_tiers_override: list[str] | None = None,
119
+ allowed_lifecycle_statuses_override: list[str] | None = None,
120
+ max_token_estimate_override: int | None = None,
121
+ max_content_size_bytes_override: int | None = None,
122
+ cwd: Path | None = None,
123
+ ) -> PolicyContext:
124
+ """Build one effective policy object from defaults, workspace policy, and CLI."""
125
+
126
+ default_policy = PolicyContext()
127
+ workspace_policy_config = _policy_config(
128
+ lambda: load_workspace_aptitude_config(cwd),
129
+ "workspace config",
130
+ )
131
+ policy = _apply_policy_override(
132
+ default_policy, workspace_policy_config, source="workspace_config"
133
+ )
134
+ cli_policy_config = PolicyConfig(
135
+ allowed_trust_tiers=allowed_trust_tiers_override,
136
+ allowed_lifecycle_statuses=allowed_lifecycle_statuses_override,
137
+ max_token_estimate=max_token_estimate_override,
138
+ max_content_size_bytes=max_content_size_bytes_override,
139
+ )
140
+ has_cli_override = any(
141
+ value is not None
142
+ for value in (
143
+ allowed_trust_tiers_override,
144
+ allowed_lifecycle_statuses_override,
145
+ max_token_estimate_override,
146
+ max_content_size_bytes_override,
147
+ )
148
+ )
149
+ if has_cli_override:
150
+ policy = _apply_policy_override(
151
+ policy, cli_policy_config, source="cli_override"
152
+ )
153
+
154
+ return policy
155
+
156
+
157
+ def _apply_policy_override(
158
+ base: PolicyContext,
159
+ override: PolicyConfig | None,
160
+ *,
161
+ source: str,
162
+ ) -> PolicyContext:
163
+ """Apply one stricter-only policy override layer onto an existing policy."""
164
+
165
+ if override is None:
166
+ return base
167
+
168
+ try:
169
+ validated_override = PolicyContext(
170
+ profile=base.profile,
171
+ source=source,
172
+ allowed_lifecycle_statuses=(
173
+ list(override.allowed_lifecycle_statuses)
174
+ if override.allowed_lifecycle_statuses is not None
175
+ else list(base.allowed_lifecycle_statuses)
176
+ ),
177
+ allowed_trust_tiers=(
178
+ list(override.allowed_trust_tiers)
179
+ if override.allowed_trust_tiers is not None
180
+ else list(base.allowed_trust_tiers)
181
+ ),
182
+ max_token_estimate=override.max_token_estimate,
183
+ max_content_size_bytes=override.max_content_size_bytes,
184
+ max_total_token_estimate=override.max_total_token_estimate,
185
+ max_total_content_size_bytes=override.max_total_content_size_bytes,
186
+ )
187
+ return PolicyContext(
188
+ profile=base.profile,
189
+ source=source,
190
+ allowed_lifecycle_statuses=_stricter_allowed_values(
191
+ base.allowed_lifecycle_statuses,
192
+ validated_override.allowed_lifecycle_statuses
193
+ if override.allowed_lifecycle_statuses is not None
194
+ else None,
195
+ ),
196
+ allowed_trust_tiers=_stricter_allowed_values(
197
+ base.allowed_trust_tiers,
198
+ validated_override.allowed_trust_tiers
199
+ if override.allowed_trust_tiers is not None
200
+ else None,
201
+ ),
202
+ max_token_estimate=_stricter_ceiling(
203
+ base.max_token_estimate,
204
+ validated_override.max_token_estimate,
205
+ ),
206
+ max_content_size_bytes=_stricter_ceiling(
207
+ base.max_content_size_bytes,
208
+ validated_override.max_content_size_bytes,
209
+ ),
210
+ max_total_token_estimate=_stricter_ceiling(
211
+ base.max_total_token_estimate,
212
+ validated_override.max_total_token_estimate,
213
+ ),
214
+ max_total_content_size_bytes=_stricter_ceiling(
215
+ base.max_total_content_size_bytes,
216
+ validated_override.max_total_content_size_bytes,
217
+ ),
218
+ )
219
+ except ValueError as exc:
220
+ error_source = (
221
+ "workspace config" if source == "workspace_config" else "CLI override"
222
+ )
223
+ raise InvalidResolverConfigurationError(error_source, str(exc)) from exc
224
+
225
+
226
+ def _policy_config(
227
+ loader: Callable[[], AptitudeConfig | None],
228
+ source_name: str,
229
+ ) -> PolicyConfig | None:
230
+ """Load one optional config source and return just its policy section."""
231
+
232
+ try:
233
+ config = loader()
234
+ except ValueError as exc:
235
+ raise InvalidResolverConfigurationError(source_name, str(exc)) from exc
236
+ if config is None:
237
+ return None
238
+ return config.policy
239
+
240
+
241
+ def _stricter_allowed_values(base: list[str], override: list[str] | None) -> list[str]:
242
+ if override is None:
243
+ return list(base)
244
+ override_set = set(override)
245
+ return [value for value in base if value in override_set]
246
+
247
+
248
+ def _stricter_ceiling(base: int | None, override: int | None) -> int | None:
249
+ if override is None:
250
+ return base
251
+ if base is None:
252
+ return override
253
+ return min(base, override)
254
+
255
+
256
+ def _selection_config(
257
+ loader: Callable[[], AptitudeConfig | None],
258
+ source_name: str,
259
+ ) -> SelectionConfig | None:
260
+ """Load one optional config source and return just its selection section."""
261
+
262
+ try:
263
+ config = loader()
264
+ except ValueError as exc:
265
+ raise InvalidResolverConfigurationError(source_name, str(exc)) from exc
266
+ if config is None:
267
+ return None
268
+ return config.selection
269
+
270
+
271
+ def build_resolve_use_case(
272
+ *,
273
+ selection_profile_override: str | None = None,
274
+ interaction_mode_override: str | None = None,
275
+ allowed_trust_tiers_override: list[str] | None = None,
276
+ allowed_lifecycle_statuses_override: list[str] | None = None,
277
+ max_token_estimate_override: int | None = None,
278
+ max_content_size_bytes_override: int | None = None,
279
+ cwd: Path | None = None,
280
+ ) -> tuple[ResolveSkillQueryUseCase, Callable[[], None]]:
281
+ """Create the resolve use case and its cleanup hook."""
282
+
283
+ registry_client, close = build_registry_client()
284
+ return (
285
+ ResolveSkillQueryUseCase(
286
+ registry_client,
287
+ policy_context=_effective_policy_context(
288
+ allowed_trust_tiers_override=allowed_trust_tiers_override,
289
+ allowed_lifecycle_statuses_override=allowed_lifecycle_statuses_override,
290
+ max_token_estimate_override=max_token_estimate_override,
291
+ max_content_size_bytes_override=max_content_size_bytes_override,
292
+ cwd=cwd,
293
+ ),
294
+ selection_preferences=_effective_selection_preferences(
295
+ selection_profile_override=selection_profile_override,
296
+ interaction_mode_override=interaction_mode_override,
297
+ cwd=cwd,
298
+ ),
299
+ ),
300
+ close,
301
+ )
302
+
303
+
304
+ def build_install_use_case(
305
+ *,
306
+ selection_profile_override: str | None = None,
307
+ interaction_mode_override: str | None = None,
308
+ allowed_trust_tiers_override: list[str] | None = None,
309
+ allowed_lifecycle_statuses_override: list[str] | None = None,
310
+ max_token_estimate_override: int | None = None,
311
+ max_content_size_bytes_override: int | None = None,
312
+ cwd: Path | None = None,
313
+ ) -> tuple[InstallSkillUseCase, Callable[[], None]]:
314
+ """Create the install use case and its cleanup hook."""
315
+
316
+ registry_client, close = build_registry_client()
317
+ return (
318
+ InstallSkillUseCase(
319
+ registry_client,
320
+ policy_context=_effective_policy_context(
321
+ allowed_trust_tiers_override=allowed_trust_tiers_override,
322
+ allowed_lifecycle_statuses_override=allowed_lifecycle_statuses_override,
323
+ max_token_estimate_override=max_token_estimate_override,
324
+ max_content_size_bytes_override=max_content_size_bytes_override,
325
+ cwd=cwd,
326
+ ),
327
+ selection_preferences=_effective_selection_preferences(
328
+ selection_profile_override=selection_profile_override,
329
+ interaction_mode_override=interaction_mode_override,
330
+ cwd=cwd,
331
+ ),
332
+ ),
333
+ close,
334
+ )
335
+
336
+
337
+ def build_sync_use_case() -> tuple[SyncFromLockUseCase, Callable[[], None]]:
338
+ """Create the sync use case and its cleanup hook."""
339
+
340
+ registry_client, close = build_registry_client()
341
+ return SyncFromLockUseCase(registry_client), close
@@ -0,0 +1,59 @@
1
+ """Application DTO package."""
2
+
3
+ from aptitude.application.dto.install_dto import (
4
+ InstallRequestDto,
5
+ InstallResultDto,
6
+ InstalledSkillDto,
7
+ SyncRequestDto,
8
+ SyncResultDto,
9
+ )
10
+ from aptitude.application.dto.resolve_request_dto import ResolveQueryRequestDto
11
+ from aptitude.application.dto.resolve_result_dto import (
12
+ ConflictDto,
13
+ DiscoveryCandidateDto,
14
+ ExecutionPlanDto,
15
+ ExecutionStepDto,
16
+ GovernanceSnapshotDto,
17
+ LockedEdgeDto,
18
+ LockedSkillDto,
19
+ LockfileDto,
20
+ LockRootDto,
21
+ PolicySnapshotDto,
22
+ SelectionSnapshotDto,
23
+ PolicyEvaluationDto,
24
+ ResolvedEdgeDto,
25
+ ResolvedGraphDto,
26
+ ResolvedSkillNodeDto,
27
+ ResolveCoordinateDto,
28
+ ResolveQueryResultDto,
29
+ ResolveSkillSummaryDto,
30
+ TraceEntryDto,
31
+ )
32
+
33
+ __all__ = [
34
+ "ConflictDto",
35
+ "DiscoveryCandidateDto",
36
+ "ExecutionPlanDto",
37
+ "ExecutionStepDto",
38
+ "GovernanceSnapshotDto",
39
+ "InstallRequestDto",
40
+ "InstallResultDto",
41
+ "InstalledSkillDto",
42
+ "LockedEdgeDto",
43
+ "LockedSkillDto",
44
+ "LockfileDto",
45
+ "LockRootDto",
46
+ "PolicySnapshotDto",
47
+ "SelectionSnapshotDto",
48
+ "PolicyEvaluationDto",
49
+ "ResolvedEdgeDto",
50
+ "ResolvedGraphDto",
51
+ "ResolvedSkillNodeDto",
52
+ "ResolveCoordinateDto",
53
+ "ResolveQueryRequestDto",
54
+ "ResolveQueryResultDto",
55
+ "ResolveSkillSummaryDto",
56
+ "SyncRequestDto",
57
+ "SyncResultDto",
58
+ "TraceEntryDto",
59
+ ]
@@ -0,0 +1,88 @@
1
+ """DTOs for install and local materialization flows."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Literal
7
+
8
+ from pydantic import BaseModel, ConfigDict, Field
9
+
10
+ from aptitude.application.dto.resolve_result_dto import (
11
+ DiscoveryCandidateDto,
12
+ ExecutionPlanDto,
13
+ LockfileDto,
14
+ PolicyEvaluationDto,
15
+ ResolvedGraphDto,
16
+ ResolveCoordinateDto,
17
+ TraceEntryDto,
18
+ )
19
+
20
+
21
+ class InstallRequestDto(BaseModel):
22
+ """Install request coming from the CLI layer."""
23
+
24
+ model_config = ConfigDict(frozen=True)
25
+
26
+ query: str
27
+ target: Path
28
+ version: str | None = None
29
+ select_slug: str | None = None
30
+ interaction_mode: Literal["auto", "always", "never"] | None = None
31
+ prompt_capable: bool = False
32
+ selection_source: str | None = None
33
+
34
+
35
+ class SyncRequestDto(BaseModel):
36
+ """Sync request coming from the CLI layer."""
37
+
38
+ model_config = ConfigDict(frozen=True)
39
+
40
+ lock_path: Path
41
+ target: Path
42
+
43
+
44
+ class InstalledSkillDto(BaseModel):
45
+ """One exact coordinate materialized locally."""
46
+
47
+ model_config = ConfigDict(frozen=True)
48
+
49
+ slug: str
50
+ version: str
51
+ install_path: str
52
+
53
+
54
+ class InstallResultDto(BaseModel):
55
+ """Install command output."""
56
+
57
+ model_config = ConfigDict(frozen=True)
58
+
59
+ requested_query: str
60
+ requested_version: str | None = None
61
+ status: Literal["selection_required", "installed"]
62
+ selection_mode: str | None = None
63
+ candidates: list[DiscoveryCandidateDto] = Field(default_factory=list)
64
+ selected_coordinate: ResolveCoordinateDto | None = None
65
+ graph: ResolvedGraphDto | None = None
66
+ lockfile: LockfileDto | None = None
67
+ execution_plan: ExecutionPlanDto | None = None
68
+ installed_skills: list[InstalledSkillDto] = Field(default_factory=list)
69
+ materialized_root: str | None = None
70
+ trace: list[TraceEntryDto] = Field(default_factory=list)
71
+ policy_evaluations: list[PolicyEvaluationDto] = Field(default_factory=list)
72
+
73
+
74
+ class SyncResultDto(BaseModel):
75
+ """Sync command output."""
76
+
77
+ model_config = ConfigDict(frozen=True)
78
+
79
+ lock_path: str
80
+ requested_query: str
81
+ status: Literal["synced"]
82
+ selection_mode: str | None = None
83
+ selected_coordinate: ResolveCoordinateDto | None = None
84
+ lockfile: LockfileDto
85
+ execution_plan: ExecutionPlanDto
86
+ installed_skills: list[InstalledSkillDto] = Field(default_factory=list)
87
+ materialized_root: str | None = None
88
+ trace: list[TraceEntryDto] = Field(default_factory=list)
@@ -0,0 +1,20 @@
1
+ """Request DTOs for discovery-backed skill resolution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Literal
6
+
7
+ from pydantic import BaseModel, ConfigDict
8
+
9
+
10
+ class ResolveQueryRequestDto(BaseModel):
11
+ """Query-driven input for discovery-backed resolution."""
12
+
13
+ model_config = ConfigDict(frozen=True)
14
+
15
+ query: str
16
+ version: str | None = None
17
+ select_slug: str | None = None
18
+ interaction_mode: Literal["auto", "always", "never"] | None = None
19
+ prompt_capable: bool = False
20
+ selection_source: str | None = None