truthound-dashboard 1.3.1__py3-none-any.whl → 1.4.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 (169) hide show
  1. truthound_dashboard/api/alerts.py +258 -0
  2. truthound_dashboard/api/anomaly.py +1302 -0
  3. truthound_dashboard/api/cross_alerts.py +352 -0
  4. truthound_dashboard/api/deps.py +143 -0
  5. truthound_dashboard/api/drift_monitor.py +540 -0
  6. truthound_dashboard/api/lineage.py +1151 -0
  7. truthound_dashboard/api/maintenance.py +363 -0
  8. truthound_dashboard/api/middleware.py +373 -1
  9. truthound_dashboard/api/model_monitoring.py +805 -0
  10. truthound_dashboard/api/notifications_advanced.py +2452 -0
  11. truthound_dashboard/api/plugins.py +2096 -0
  12. truthound_dashboard/api/profile.py +211 -14
  13. truthound_dashboard/api/reports.py +853 -0
  14. truthound_dashboard/api/router.py +147 -0
  15. truthound_dashboard/api/rule_suggestions.py +310 -0
  16. truthound_dashboard/api/schema_evolution.py +231 -0
  17. truthound_dashboard/api/sources.py +47 -3
  18. truthound_dashboard/api/triggers.py +190 -0
  19. truthound_dashboard/api/validations.py +13 -0
  20. truthound_dashboard/api/validators.py +333 -4
  21. truthound_dashboard/api/versioning.py +309 -0
  22. truthound_dashboard/api/websocket.py +301 -0
  23. truthound_dashboard/core/__init__.py +27 -0
  24. truthound_dashboard/core/anomaly.py +1395 -0
  25. truthound_dashboard/core/anomaly_explainer.py +633 -0
  26. truthound_dashboard/core/cache.py +206 -0
  27. truthound_dashboard/core/cached_services.py +422 -0
  28. truthound_dashboard/core/charts.py +352 -0
  29. truthound_dashboard/core/connections.py +1069 -42
  30. truthound_dashboard/core/cross_alerts.py +837 -0
  31. truthound_dashboard/core/drift_monitor.py +1477 -0
  32. truthound_dashboard/core/drift_sampling.py +669 -0
  33. truthound_dashboard/core/i18n/__init__.py +42 -0
  34. truthound_dashboard/core/i18n/detector.py +173 -0
  35. truthound_dashboard/core/i18n/messages.py +564 -0
  36. truthound_dashboard/core/lineage.py +971 -0
  37. truthound_dashboard/core/maintenance.py +443 -5
  38. truthound_dashboard/core/model_monitoring.py +1043 -0
  39. truthound_dashboard/core/notifications/channels.py +1020 -1
  40. truthound_dashboard/core/notifications/deduplication/__init__.py +143 -0
  41. truthound_dashboard/core/notifications/deduplication/policies.py +274 -0
  42. truthound_dashboard/core/notifications/deduplication/service.py +400 -0
  43. truthound_dashboard/core/notifications/deduplication/stores.py +2365 -0
  44. truthound_dashboard/core/notifications/deduplication/strategies.py +422 -0
  45. truthound_dashboard/core/notifications/dispatcher.py +43 -0
  46. truthound_dashboard/core/notifications/escalation/__init__.py +149 -0
  47. truthound_dashboard/core/notifications/escalation/backends.py +1384 -0
  48. truthound_dashboard/core/notifications/escalation/engine.py +429 -0
  49. truthound_dashboard/core/notifications/escalation/models.py +336 -0
  50. truthound_dashboard/core/notifications/escalation/scheduler.py +1187 -0
  51. truthound_dashboard/core/notifications/escalation/state_machine.py +330 -0
  52. truthound_dashboard/core/notifications/escalation/stores.py +2896 -0
  53. truthound_dashboard/core/notifications/events.py +49 -0
  54. truthound_dashboard/core/notifications/metrics/__init__.py +115 -0
  55. truthound_dashboard/core/notifications/metrics/base.py +528 -0
  56. truthound_dashboard/core/notifications/metrics/collectors.py +583 -0
  57. truthound_dashboard/core/notifications/routing/__init__.py +169 -0
  58. truthound_dashboard/core/notifications/routing/combinators.py +184 -0
  59. truthound_dashboard/core/notifications/routing/config.py +375 -0
  60. truthound_dashboard/core/notifications/routing/config_parser.py +867 -0
  61. truthound_dashboard/core/notifications/routing/engine.py +382 -0
  62. truthound_dashboard/core/notifications/routing/expression_engine.py +1269 -0
  63. truthound_dashboard/core/notifications/routing/jinja2_engine.py +774 -0
  64. truthound_dashboard/core/notifications/routing/rules.py +625 -0
  65. truthound_dashboard/core/notifications/routing/validator.py +678 -0
  66. truthound_dashboard/core/notifications/service.py +2 -0
  67. truthound_dashboard/core/notifications/stats_aggregator.py +850 -0
  68. truthound_dashboard/core/notifications/throttling/__init__.py +83 -0
  69. truthound_dashboard/core/notifications/throttling/builder.py +311 -0
  70. truthound_dashboard/core/notifications/throttling/stores.py +1859 -0
  71. truthound_dashboard/core/notifications/throttling/throttlers.py +633 -0
  72. truthound_dashboard/core/openlineage.py +1028 -0
  73. truthound_dashboard/core/plugins/__init__.py +39 -0
  74. truthound_dashboard/core/plugins/docs/__init__.py +39 -0
  75. truthound_dashboard/core/plugins/docs/extractor.py +703 -0
  76. truthound_dashboard/core/plugins/docs/renderers.py +804 -0
  77. truthound_dashboard/core/plugins/hooks/__init__.py +63 -0
  78. truthound_dashboard/core/plugins/hooks/decorators.py +367 -0
  79. truthound_dashboard/core/plugins/hooks/manager.py +403 -0
  80. truthound_dashboard/core/plugins/hooks/protocols.py +265 -0
  81. truthound_dashboard/core/plugins/lifecycle/__init__.py +41 -0
  82. truthound_dashboard/core/plugins/lifecycle/hot_reload.py +584 -0
  83. truthound_dashboard/core/plugins/lifecycle/machine.py +419 -0
  84. truthound_dashboard/core/plugins/lifecycle/states.py +266 -0
  85. truthound_dashboard/core/plugins/loader.py +504 -0
  86. truthound_dashboard/core/plugins/registry.py +810 -0
  87. truthound_dashboard/core/plugins/reporter_executor.py +588 -0
  88. truthound_dashboard/core/plugins/sandbox/__init__.py +59 -0
  89. truthound_dashboard/core/plugins/sandbox/code_validator.py +243 -0
  90. truthound_dashboard/core/plugins/sandbox/engines.py +770 -0
  91. truthound_dashboard/core/plugins/sandbox/protocols.py +194 -0
  92. truthound_dashboard/core/plugins/sandbox.py +617 -0
  93. truthound_dashboard/core/plugins/security/__init__.py +68 -0
  94. truthound_dashboard/core/plugins/security/analyzer.py +535 -0
  95. truthound_dashboard/core/plugins/security/policies.py +311 -0
  96. truthound_dashboard/core/plugins/security/protocols.py +296 -0
  97. truthound_dashboard/core/plugins/security/signing.py +842 -0
  98. truthound_dashboard/core/plugins/security.py +446 -0
  99. truthound_dashboard/core/plugins/validator_executor.py +401 -0
  100. truthound_dashboard/core/plugins/versioning/__init__.py +51 -0
  101. truthound_dashboard/core/plugins/versioning/constraints.py +377 -0
  102. truthound_dashboard/core/plugins/versioning/dependencies.py +541 -0
  103. truthound_dashboard/core/plugins/versioning/semver.py +266 -0
  104. truthound_dashboard/core/profile_comparison.py +601 -0
  105. truthound_dashboard/core/report_history.py +570 -0
  106. truthound_dashboard/core/reporters/__init__.py +57 -0
  107. truthound_dashboard/core/reporters/base.py +296 -0
  108. truthound_dashboard/core/reporters/csv_reporter.py +155 -0
  109. truthound_dashboard/core/reporters/html_reporter.py +598 -0
  110. truthound_dashboard/core/reporters/i18n/__init__.py +65 -0
  111. truthound_dashboard/core/reporters/i18n/base.py +494 -0
  112. truthound_dashboard/core/reporters/i18n/catalogs.py +930 -0
  113. truthound_dashboard/core/reporters/json_reporter.py +160 -0
  114. truthound_dashboard/core/reporters/junit_reporter.py +233 -0
  115. truthound_dashboard/core/reporters/markdown_reporter.py +207 -0
  116. truthound_dashboard/core/reporters/pdf_reporter.py +209 -0
  117. truthound_dashboard/core/reporters/registry.py +272 -0
  118. truthound_dashboard/core/rule_generator.py +2088 -0
  119. truthound_dashboard/core/scheduler.py +822 -12
  120. truthound_dashboard/core/schema_evolution.py +858 -0
  121. truthound_dashboard/core/services.py +152 -9
  122. truthound_dashboard/core/statistics.py +718 -0
  123. truthound_dashboard/core/streaming_anomaly.py +883 -0
  124. truthound_dashboard/core/triggers/__init__.py +45 -0
  125. truthound_dashboard/core/triggers/base.py +226 -0
  126. truthound_dashboard/core/triggers/evaluators.py +609 -0
  127. truthound_dashboard/core/triggers/factory.py +363 -0
  128. truthound_dashboard/core/unified_alerts.py +870 -0
  129. truthound_dashboard/core/validation_limits.py +509 -0
  130. truthound_dashboard/core/versioning.py +709 -0
  131. truthound_dashboard/core/websocket/__init__.py +59 -0
  132. truthound_dashboard/core/websocket/manager.py +512 -0
  133. truthound_dashboard/core/websocket/messages.py +130 -0
  134. truthound_dashboard/db/__init__.py +30 -0
  135. truthound_dashboard/db/models.py +3375 -3
  136. truthound_dashboard/main.py +22 -0
  137. truthound_dashboard/schemas/__init__.py +396 -1
  138. truthound_dashboard/schemas/anomaly.py +1258 -0
  139. truthound_dashboard/schemas/base.py +4 -0
  140. truthound_dashboard/schemas/cross_alerts.py +334 -0
  141. truthound_dashboard/schemas/drift_monitor.py +890 -0
  142. truthound_dashboard/schemas/lineage.py +428 -0
  143. truthound_dashboard/schemas/maintenance.py +154 -0
  144. truthound_dashboard/schemas/model_monitoring.py +374 -0
  145. truthound_dashboard/schemas/notifications_advanced.py +1363 -0
  146. truthound_dashboard/schemas/openlineage.py +704 -0
  147. truthound_dashboard/schemas/plugins.py +1293 -0
  148. truthound_dashboard/schemas/profile.py +420 -34
  149. truthound_dashboard/schemas/profile_comparison.py +242 -0
  150. truthound_dashboard/schemas/reports.py +285 -0
  151. truthound_dashboard/schemas/rule_suggestion.py +434 -0
  152. truthound_dashboard/schemas/schema_evolution.py +164 -0
  153. truthound_dashboard/schemas/source.py +117 -2
  154. truthound_dashboard/schemas/triggers.py +511 -0
  155. truthound_dashboard/schemas/unified_alerts.py +223 -0
  156. truthound_dashboard/schemas/validation.py +25 -1
  157. truthound_dashboard/schemas/validators/__init__.py +11 -0
  158. truthound_dashboard/schemas/validators/base.py +151 -0
  159. truthound_dashboard/schemas/versioning.py +152 -0
  160. truthound_dashboard/static/index.html +2 -2
  161. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/METADATA +147 -23
  162. truthound_dashboard-1.4.1.dist-info/RECORD +239 -0
  163. truthound_dashboard/static/assets/index-BZG20KuF.js +0 -586
  164. truthound_dashboard/static/assets/index-D_HyZ3pb.css +0 -1
  165. truthound_dashboard/static/assets/unmerged_dictionaries-CtpqQBm0.js +0 -1
  166. truthound_dashboard-1.3.1.dist-info/RECORD +0 -110
  167. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/WHEEL +0 -0
  168. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/entry_points.txt +0 -0
  169. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,1293 @@
1
+ """Pydantic schemas for Plugin System.
2
+
3
+ This module defines schemas for plugin management including:
4
+ - Plugin metadata and versioning
5
+ - Custom validators
6
+ - Custom reporters
7
+ - Plugin marketplace
8
+ - Security and sandboxing
9
+ """
10
+
11
+ from datetime import datetime
12
+ from enum import Enum
13
+ from typing import Any, Literal
14
+
15
+ from pydantic import Field, field_validator
16
+
17
+ from .base import BaseSchema, IDMixin, ListResponseWrapper, TimestampMixin
18
+
19
+
20
+ # =============================================================================
21
+ # Enums
22
+ # =============================================================================
23
+
24
+
25
+ class PluginType(str, Enum):
26
+ """Type of plugin."""
27
+
28
+ VALIDATOR = "validator"
29
+ REPORTER = "reporter"
30
+ CONNECTOR = "connector"
31
+ TRANSFORMER = "transformer"
32
+
33
+
34
+ class PluginStatus(str, Enum):
35
+ """Installation status of a plugin."""
36
+
37
+ AVAILABLE = "available"
38
+ INSTALLED = "installed"
39
+ ENABLED = "enabled"
40
+ DISABLED = "disabled"
41
+ UPDATE_AVAILABLE = "update_available"
42
+ ERROR = "error"
43
+
44
+
45
+ class PluginSource(str, Enum):
46
+ """Source of the plugin."""
47
+
48
+ OFFICIAL = "official"
49
+ COMMUNITY = "community"
50
+ LOCAL = "local"
51
+ PRIVATE = "private"
52
+
53
+
54
+ class SecurityLevel(str, Enum):
55
+ """Security level of the plugin."""
56
+
57
+ TRUSTED = "trusted"
58
+ VERIFIED = "verified"
59
+ UNVERIFIED = "unverified"
60
+ SANDBOXED = "sandboxed"
61
+
62
+
63
+ class ValidatorParamType(str, Enum):
64
+ """Parameter types for custom validators."""
65
+
66
+ STRING = "string"
67
+ INTEGER = "integer"
68
+ FLOAT = "float"
69
+ BOOLEAN = "boolean"
70
+ COLUMN = "column"
71
+ COLUMN_LIST = "column_list"
72
+ SELECT = "select"
73
+ MULTI_SELECT = "multi_select"
74
+ REGEX = "regex"
75
+ JSON = "json"
76
+
77
+
78
+ class ReporterOutputFormat(str, Enum):
79
+ """Output format for custom reporters."""
80
+
81
+ PDF = "pdf"
82
+ HTML = "html"
83
+ JSON = "json"
84
+ CSV = "csv"
85
+ EXCEL = "excel"
86
+ MARKDOWN = "markdown"
87
+ CUSTOM = "custom"
88
+
89
+
90
+ # =============================================================================
91
+ # Plugin Version Management
92
+ # =============================================================================
93
+
94
+
95
+ class SemverVersion(BaseSchema):
96
+ """Semantic version representation."""
97
+
98
+ major: int = Field(ge=0, description="Major version (breaking changes)")
99
+ minor: int = Field(ge=0, description="Minor version (new features)")
100
+ patch: int = Field(ge=0, description="Patch version (bug fixes)")
101
+ prerelease: str | None = Field(
102
+ default=None, description="Pre-release identifier (e.g., 'beta.1')"
103
+ )
104
+ build: str | None = Field(default=None, description="Build metadata")
105
+
106
+ def __str__(self) -> str:
107
+ version = f"{self.major}.{self.minor}.{self.patch}"
108
+ if self.prerelease:
109
+ version += f"-{self.prerelease}"
110
+ if self.build:
111
+ version += f"+{self.build}"
112
+ return version
113
+
114
+ @classmethod
115
+ def parse(cls, version_str: str) -> "SemverVersion":
116
+ """Parse a version string into SemverVersion."""
117
+ import re
118
+
119
+ pattern = r"^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9.]+))?(?:\+([a-zA-Z0-9.]+))?$"
120
+ match = re.match(pattern, version_str)
121
+ if not match:
122
+ raise ValueError(f"Invalid version string: {version_str}")
123
+ return cls(
124
+ major=int(match.group(1)),
125
+ minor=int(match.group(2)),
126
+ patch=int(match.group(3)),
127
+ prerelease=match.group(4),
128
+ build=match.group(5),
129
+ )
130
+
131
+
132
+ class PluginDependency(BaseSchema):
133
+ """Plugin dependency specification."""
134
+
135
+ plugin_id: str = Field(description="ID of the dependent plugin")
136
+ version_constraint: str = Field(
137
+ description="Version constraint (e.g., '>=1.0.0', '^2.0.0')"
138
+ )
139
+ optional: bool = Field(default=False, description="Whether dependency is optional")
140
+
141
+
142
+ # =============================================================================
143
+ # Plugin Security
144
+ # =============================================================================
145
+
146
+
147
+ class PluginPermission(str, Enum):
148
+ """Permissions that a plugin can request."""
149
+
150
+ READ_DATA = "read_data"
151
+ WRITE_DATA = "write_data"
152
+ NETWORK_ACCESS = "network_access"
153
+ FILE_SYSTEM = "file_system"
154
+ EXECUTE_CODE = "execute_code"
155
+ SEND_NOTIFICATIONS = "send_notifications"
156
+ ACCESS_SECRETS = "access_secrets"
157
+
158
+
159
+ class PluginSignature(BaseSchema):
160
+ """Plugin signature information for verification."""
161
+
162
+ algorithm: str = Field(default="ed25519", description="Signature algorithm")
163
+ public_key: str = Field(description="Public key for verification")
164
+ signature: str = Field(description="Signature of the plugin package")
165
+ signed_at: datetime = Field(description="Timestamp of signing")
166
+ signer_id: str | None = Field(default=None, description="ID of the signer")
167
+
168
+
169
+ class SandboxConfig(BaseSchema):
170
+ """Sandbox configuration for plugin execution."""
171
+
172
+ enabled: bool = Field(default=True, description="Whether sandbox is enabled")
173
+ memory_limit_mb: int = Field(
174
+ default=256, ge=64, le=2048, description="Memory limit in MB"
175
+ )
176
+ cpu_time_limit_seconds: int = Field(
177
+ default=30, ge=1, le=300, description="CPU time limit in seconds"
178
+ )
179
+ network_enabled: bool = Field(
180
+ default=False, description="Whether network access is allowed"
181
+ )
182
+ allowed_modules: list[str] = Field(
183
+ default_factory=list, description="List of allowed Python modules"
184
+ )
185
+ blocked_modules: list[str] = Field(
186
+ default_factory=lambda: ["os", "subprocess", "sys", "shutil"],
187
+ description="List of blocked Python modules",
188
+ )
189
+ max_file_size_mb: int = Field(
190
+ default=10, ge=1, le=100, description="Max file size in MB"
191
+ )
192
+
193
+
194
+ class SecurityReport(BaseSchema):
195
+ """Security analysis report for a plugin."""
196
+
197
+ plugin_id: str
198
+ analyzed_at: datetime
199
+ risk_level: SecurityLevel
200
+ issues: list[str] = Field(default_factory=list)
201
+ warnings: list[str] = Field(default_factory=list)
202
+ permissions_required: list[PluginPermission] = Field(default_factory=list)
203
+ signature_valid: bool = Field(default=False)
204
+ sandbox_compatible: bool = Field(default=True)
205
+
206
+
207
+ # =============================================================================
208
+ # Custom Validator Plugin
209
+ # =============================================================================
210
+
211
+
212
+ class ValidatorParamDefinition(BaseSchema):
213
+ """Definition of a validator parameter."""
214
+
215
+ name: str = Field(description="Parameter name")
216
+ type: ValidatorParamType = Field(description="Parameter type")
217
+ description: str = Field(description="Parameter description")
218
+ required: bool = Field(default=False, description="Whether parameter is required")
219
+ default: Any = Field(default=None, description="Default value")
220
+ options: list[str] | None = Field(
221
+ default=None, description="Options for select/multi_select types"
222
+ )
223
+ min_value: float | None = Field(
224
+ default=None, description="Minimum value for numeric types"
225
+ )
226
+ max_value: float | None = Field(
227
+ default=None, description="Maximum value for numeric types"
228
+ )
229
+ pattern: str | None = Field(
230
+ default=None, description="Regex pattern for string validation"
231
+ )
232
+
233
+
234
+ class CustomValidatorBase(BaseSchema):
235
+ """Base schema for custom validator."""
236
+
237
+ name: str = Field(max_length=100, description="Validator name")
238
+ display_name: str = Field(max_length=200, description="Display name for UI")
239
+ description: str = Field(description="Validator description")
240
+ category: str = Field(max_length=50, description="Validator category")
241
+ severity: Literal["error", "warning", "info"] = Field(
242
+ default="error", description="Default severity"
243
+ )
244
+ tags: list[str] = Field(default_factory=list, description="Tags for search/filter")
245
+ parameters: list[ValidatorParamDefinition] = Field(
246
+ default_factory=list, description="Validator parameters"
247
+ )
248
+ code: str = Field(description="Python code implementing the validator")
249
+ test_cases: list[dict[str, Any]] = Field(
250
+ default_factory=list, description="Test cases for validation"
251
+ )
252
+
253
+
254
+ class CustomValidatorCreate(CustomValidatorBase):
255
+ """Schema for creating a custom validator."""
256
+
257
+ plugin_id: str | None = Field(
258
+ default=None, description="Associated plugin ID if part of a plugin"
259
+ )
260
+
261
+
262
+ class CustomValidatorUpdate(BaseSchema):
263
+ """Schema for updating a custom validator."""
264
+
265
+ display_name: str | None = None
266
+ description: str | None = None
267
+ category: str | None = None
268
+ severity: Literal["error", "warning", "info"] | None = None
269
+ tags: list[str] | None = None
270
+ parameters: list[ValidatorParamDefinition] | None = None
271
+ code: str | None = None
272
+ test_cases: list[dict[str, Any]] | None = None
273
+ is_enabled: bool | None = None
274
+
275
+
276
+ class CustomValidatorResponse(CustomValidatorBase, IDMixin, TimestampMixin):
277
+ """Response schema for custom validator."""
278
+
279
+ plugin_id: str | None = None
280
+ is_enabled: bool = True
281
+ is_verified: bool = False
282
+ usage_count: int = 0
283
+ last_used_at: datetime | None = None
284
+
285
+ @classmethod
286
+ def from_model(cls, model: Any) -> "CustomValidatorResponse":
287
+ return cls(
288
+ id=str(model.id),
289
+ name=model.name,
290
+ display_name=model.display_name,
291
+ description=model.description,
292
+ category=model.category,
293
+ severity=model.severity,
294
+ tags=model.tags or [],
295
+ parameters=model.parameters or [],
296
+ code=model.code,
297
+ test_cases=model.test_cases or [],
298
+ plugin_id=str(model.plugin_id) if model.plugin_id else None,
299
+ is_enabled=model.is_enabled,
300
+ is_verified=model.is_verified,
301
+ usage_count=model.usage_count,
302
+ last_used_at=model.last_used_at,
303
+ created_at=model.created_at,
304
+ updated_at=model.updated_at,
305
+ )
306
+
307
+
308
+ class CustomValidatorListResponse(ListResponseWrapper[CustomValidatorResponse]):
309
+ """List response for custom validators."""
310
+
311
+ pass
312
+
313
+
314
+ class ValidatorTestRequest(BaseSchema):
315
+ """Request to test a custom validator."""
316
+
317
+ code: str = Field(description="Validator code to test")
318
+ parameters: list[ValidatorParamDefinition] = Field(
319
+ default_factory=list, description="Parameter definitions"
320
+ )
321
+ test_data: dict[str, Any] = Field(description="Test data (column values)")
322
+ param_values: dict[str, Any] = Field(
323
+ default_factory=dict, description="Parameter values for test"
324
+ )
325
+
326
+
327
+ class ValidatorTestResponse(BaseSchema):
328
+ """Response from testing a custom validator."""
329
+
330
+ success: bool
331
+ passed: bool | None = None
332
+ execution_time_ms: float
333
+ result: dict[str, Any] | None = None
334
+ error: str | None = None
335
+ warnings: list[str] = Field(default_factory=list)
336
+
337
+
338
+ # =============================================================================
339
+ # Custom Reporter Plugin
340
+ # =============================================================================
341
+
342
+
343
+ class ReporterFieldDefinition(BaseSchema):
344
+ """Definition of a reporter configuration field."""
345
+
346
+ name: str = Field(description="Field name")
347
+ type: str = Field(description="Field type (string, boolean, select, etc.)")
348
+ label: str = Field(description="Display label")
349
+ description: str | None = Field(default=None, description="Field description")
350
+ required: bool = Field(default=False)
351
+ default: Any = Field(default=None)
352
+ options: list[dict[str, str]] | None = Field(
353
+ default=None, description="Options for select fields"
354
+ )
355
+
356
+
357
+ class CustomReporterBase(BaseSchema):
358
+ """Base schema for custom reporter."""
359
+
360
+ name: str = Field(max_length=100, description="Reporter name")
361
+ display_name: str = Field(max_length=200, description="Display name for UI")
362
+ description: str = Field(description="Reporter description")
363
+ output_formats: list[ReporterOutputFormat] = Field(
364
+ default_factory=lambda: [ReporterOutputFormat.HTML],
365
+ description="Supported output formats",
366
+ )
367
+ config_fields: list[ReporterFieldDefinition] = Field(
368
+ default_factory=list, description="Configuration fields"
369
+ )
370
+ template: str | None = Field(
371
+ default=None, description="Jinja2 template for report generation"
372
+ )
373
+ code: str | None = Field(
374
+ default=None, description="Python code for custom report generation"
375
+ )
376
+ preview_image_url: str | None = Field(
377
+ default=None, description="Preview image URL"
378
+ )
379
+
380
+
381
+ class CustomReporterCreate(CustomReporterBase):
382
+ """Schema for creating a custom reporter."""
383
+
384
+ plugin_id: str | None = Field(default=None, description="Associated plugin ID")
385
+
386
+
387
+ class CustomReporterUpdate(BaseSchema):
388
+ """Schema for updating a custom reporter."""
389
+
390
+ display_name: str | None = None
391
+ description: str | None = None
392
+ output_formats: list[ReporterOutputFormat] | None = None
393
+ config_fields: list[ReporterFieldDefinition] | None = None
394
+ template: str | None = None
395
+ code: str | None = None
396
+ preview_image_url: str | None = None
397
+ is_enabled: bool | None = None
398
+
399
+
400
+ class CustomReporterResponse(CustomReporterBase, IDMixin, TimestampMixin):
401
+ """Response schema for custom reporter."""
402
+
403
+ plugin_id: str | None = None
404
+ is_enabled: bool = True
405
+ is_verified: bool = False
406
+ usage_count: int = 0
407
+
408
+ @classmethod
409
+ def from_model(cls, model: Any) -> "CustomReporterResponse":
410
+ return cls(
411
+ id=str(model.id),
412
+ name=model.name,
413
+ display_name=model.display_name,
414
+ description=model.description,
415
+ output_formats=model.output_formats or [ReporterOutputFormat.HTML],
416
+ config_fields=model.config_fields or [],
417
+ template=model.template,
418
+ code=model.code,
419
+ preview_image_url=model.preview_image_url,
420
+ plugin_id=str(model.plugin_id) if model.plugin_id else None,
421
+ is_enabled=model.is_enabled,
422
+ is_verified=model.is_verified,
423
+ usage_count=model.usage_count,
424
+ created_at=model.created_at,
425
+ updated_at=model.updated_at,
426
+ )
427
+
428
+
429
+ class CustomReporterListResponse(ListResponseWrapper[CustomReporterResponse]):
430
+ """List response for custom reporters."""
431
+
432
+ pass
433
+
434
+
435
+ class ReporterGenerateRequest(BaseSchema):
436
+ """Request to generate a report using a custom reporter."""
437
+
438
+ output_format: ReporterOutputFormat = Field(description="Desired output format")
439
+ config: dict[str, Any] = Field(
440
+ default_factory=dict, description="Reporter configuration"
441
+ )
442
+ # Either validation_id or data must be provided
443
+ validation_id: str | None = Field(
444
+ default=None,
445
+ description="Validation ID to generate report from (auto-fetches data)",
446
+ )
447
+ data: dict[str, Any] | None = Field(
448
+ default=None,
449
+ description="Data to include in report (use if not providing validation_id)",
450
+ )
451
+ source_ids: list[str] | None = Field(
452
+ default=None, description="Source IDs to include"
453
+ )
454
+
455
+
456
+ class ReporterGenerateResponse(BaseSchema):
457
+ """Response from generating a report."""
458
+
459
+ success: bool
460
+ report_id: str | None = None
461
+ download_url: str | None = None
462
+ preview_html: str | None = None
463
+ error: str | None = None
464
+ generation_time_ms: float = 0
465
+
466
+
467
+ # =============================================================================
468
+ # Plugin Metadata
469
+ # =============================================================================
470
+
471
+
472
+ class PluginAuthor(BaseSchema):
473
+ """Plugin author information."""
474
+
475
+ name: str = Field(description="Author name")
476
+ email: str | None = Field(default=None, description="Author email")
477
+ url: str | None = Field(default=None, description="Author website")
478
+
479
+
480
+ class PluginMetadata(BaseSchema):
481
+ """Metadata for a plugin."""
482
+
483
+ name: str = Field(max_length=100, description="Plugin name")
484
+ display_name: str = Field(max_length=200, description="Display name for UI")
485
+ description: str = Field(description="Plugin description")
486
+ version: str = Field(description="Plugin version (semver)")
487
+ type: PluginType = Field(description="Plugin type")
488
+ source: PluginSource = Field(default=PluginSource.COMMUNITY)
489
+ author: PluginAuthor | None = None
490
+ license: str | None = Field(default=None, description="License identifier")
491
+ homepage: str | None = Field(default=None, description="Plugin homepage URL")
492
+ repository: str | None = Field(default=None, description="Repository URL")
493
+ keywords: list[str] = Field(default_factory=list, description="Search keywords")
494
+ categories: list[str] = Field(default_factory=list, description="Plugin categories")
495
+ dependencies: list[PluginDependency] = Field(
496
+ default_factory=list, description="Plugin dependencies"
497
+ )
498
+ python_version: str | None = Field(
499
+ default=None, description="Required Python version"
500
+ )
501
+ dashboard_version: str | None = Field(
502
+ default=None, description="Required dashboard version"
503
+ )
504
+ permissions: list[PluginPermission] = Field(
505
+ default_factory=list, description="Required permissions"
506
+ )
507
+
508
+ @field_validator("version")
509
+ @classmethod
510
+ def validate_version(cls, v: str) -> str:
511
+ SemverVersion.parse(v) # Validate semver format
512
+ return v
513
+
514
+
515
+ class PluginBase(PluginMetadata):
516
+ """Base schema for plugin."""
517
+
518
+ icon_url: str | None = Field(default=None, description="Plugin icon URL")
519
+ banner_url: str | None = Field(default=None, description="Plugin banner URL")
520
+ documentation_url: str | None = Field(
521
+ default=None, description="Documentation URL"
522
+ )
523
+ changelog: str | None = Field(default=None, description="Changelog markdown")
524
+ readme: str | None = Field(default=None, description="README markdown")
525
+
526
+
527
+ class PluginCreate(PluginBase):
528
+ """Schema for creating/registering a plugin."""
529
+
530
+ package_url: str | None = Field(
531
+ default=None, description="URL to download plugin package"
532
+ )
533
+ signature: PluginSignature | None = Field(
534
+ default=None, description="Plugin signature"
535
+ )
536
+ sandbox_config: SandboxConfig | None = Field(
537
+ default=None, description="Sandbox configuration"
538
+ )
539
+
540
+
541
+ class PluginUpdate(BaseSchema):
542
+ """Schema for updating a plugin."""
543
+
544
+ display_name: str | None = None
545
+ description: str | None = None
546
+ icon_url: str | None = None
547
+ banner_url: str | None = None
548
+ documentation_url: str | None = None
549
+ changelog: str | None = None
550
+ readme: str | None = None
551
+ is_enabled: bool | None = None
552
+ sandbox_config: SandboxConfig | None = None
553
+
554
+
555
+ class PluginResponse(PluginBase, IDMixin, TimestampMixin):
556
+ """Response schema for a plugin."""
557
+
558
+ status: PluginStatus = Field(default=PluginStatus.AVAILABLE)
559
+ security_level: SecurityLevel = Field(default=SecurityLevel.UNVERIFIED)
560
+ installed_version: str | None = Field(
561
+ default=None, description="Currently installed version"
562
+ )
563
+ latest_version: str | None = Field(
564
+ default=None, description="Latest available version"
565
+ )
566
+ is_enabled: bool = Field(default=False)
567
+ install_count: int = Field(default=0, description="Total install count")
568
+ rating: float | None = Field(
569
+ default=None, ge=0, le=5, description="Average rating"
570
+ )
571
+ rating_count: int = Field(default=0, description="Number of ratings")
572
+ validators_count: int = Field(default=0, description="Number of validators")
573
+ reporters_count: int = Field(default=0, description="Number of reporters")
574
+ last_updated: datetime | None = None
575
+ installed_at: datetime | None = None
576
+
577
+ @classmethod
578
+ def from_model(cls, model: Any) -> "PluginResponse":
579
+ return cls(
580
+ id=str(model.id),
581
+ name=model.name,
582
+ display_name=model.display_name,
583
+ description=model.description,
584
+ version=model.version,
585
+ type=model.type,
586
+ source=model.source,
587
+ author=model.author,
588
+ license=model.license,
589
+ homepage=model.homepage,
590
+ repository=model.repository,
591
+ keywords=model.keywords or [],
592
+ categories=model.categories or [],
593
+ dependencies=model.dependencies or [],
594
+ python_version=model.python_version,
595
+ dashboard_version=model.dashboard_version,
596
+ permissions=model.permissions or [],
597
+ icon_url=model.icon_url,
598
+ banner_url=model.banner_url,
599
+ documentation_url=model.documentation_url,
600
+ changelog=model.changelog,
601
+ readme=model.readme,
602
+ status=model.status,
603
+ security_level=model.security_level,
604
+ installed_version=model.installed_version,
605
+ latest_version=model.latest_version,
606
+ is_enabled=model.is_enabled,
607
+ install_count=model.install_count,
608
+ rating=model.rating,
609
+ rating_count=model.rating_count,
610
+ validators_count=model.validators_count,
611
+ reporters_count=model.reporters_count,
612
+ last_updated=model.last_updated,
613
+ installed_at=model.installed_at,
614
+ created_at=model.created_at,
615
+ updated_at=model.updated_at,
616
+ )
617
+
618
+
619
+ class PluginListResponse(ListResponseWrapper[PluginResponse]):
620
+ """List response for plugins."""
621
+
622
+ pass
623
+
624
+
625
+ class PluginSummary(BaseSchema):
626
+ """Summary of a plugin for list views."""
627
+
628
+ id: str
629
+ name: str
630
+ display_name: str
631
+ description: str
632
+ version: str
633
+ type: PluginType
634
+ source: PluginSource
635
+ status: PluginStatus
636
+ security_level: SecurityLevel
637
+ icon_url: str | None = None
638
+ rating: float | None = None
639
+ rating_count: int = 0
640
+ install_count: int = 0
641
+
642
+
643
+ # =============================================================================
644
+ # Plugin Marketplace
645
+ # =============================================================================
646
+
647
+
648
+ class MarketplaceSearchRequest(BaseSchema):
649
+ """Request to search plugins in marketplace."""
650
+
651
+ query: str | None = Field(default=None, description="Search query")
652
+ types: list[PluginType] | None = Field(
653
+ default=None, description="Filter by plugin types"
654
+ )
655
+ sources: list[PluginSource] | None = Field(
656
+ default=None, description="Filter by sources"
657
+ )
658
+ categories: list[str] | None = Field(
659
+ default=None, description="Filter by categories"
660
+ )
661
+ keywords: list[str] | None = Field(default=None, description="Filter by keywords")
662
+ min_rating: float | None = Field(
663
+ default=None, ge=0, le=5, description="Minimum rating"
664
+ )
665
+ verified_only: bool = Field(default=False, description="Only show verified plugins")
666
+ sort_by: Literal["relevance", "rating", "installs", "updated", "name"] = Field(
667
+ default="relevance"
668
+ )
669
+ sort_order: Literal["asc", "desc"] = Field(default="desc")
670
+ offset: int = Field(default=0, ge=0)
671
+ limit: int = Field(default=20, ge=1, le=100)
672
+
673
+
674
+ class MarketplaceCategory(BaseSchema):
675
+ """Category in the marketplace."""
676
+
677
+ name: str
678
+ display_name: str
679
+ description: str
680
+ icon: str | None = None
681
+ plugin_count: int = 0
682
+
683
+
684
+ class MarketplaceStats(BaseSchema):
685
+ """Statistics about the marketplace."""
686
+
687
+ total_plugins: int = 0
688
+ total_validators: int = 0
689
+ total_reporters: int = 0
690
+ total_installs: int = 0
691
+ categories: list[MarketplaceCategory] = Field(default_factory=list)
692
+ featured_plugins: list[PluginSummary] = Field(default_factory=list)
693
+ popular_plugins: list[PluginSummary] = Field(default_factory=list)
694
+ recent_plugins: list[PluginSummary] = Field(default_factory=list)
695
+
696
+
697
+ class PluginInstallRequest(BaseSchema):
698
+ """Request to install a plugin."""
699
+
700
+ plugin_id: str = Field(description="Plugin ID to install")
701
+ version: str | None = Field(
702
+ default=None, description="Specific version to install"
703
+ )
704
+ force: bool = Field(default=False, description="Force reinstall if exists")
705
+ enable_after_install: bool = Field(
706
+ default=True, description="Enable plugin after installation"
707
+ )
708
+ skip_verification: bool = Field(
709
+ default=False,
710
+ description="Skip security verification (use with caution)",
711
+ )
712
+
713
+
714
+ class PluginInstallResponse(BaseSchema):
715
+ """Response from plugin installation."""
716
+
717
+ success: bool
718
+ plugin_id: str
719
+ installed_version: str | None = None
720
+ message: str | None = None
721
+ warnings: list[str] = Field(default_factory=list)
722
+ security_report: SecurityReport | None = None
723
+
724
+
725
+ class PluginUninstallRequest(BaseSchema):
726
+ """Request to uninstall a plugin."""
727
+
728
+ plugin_id: str = Field(description="Plugin ID to uninstall")
729
+ remove_data: bool = Field(
730
+ default=False, description="Remove all plugin data and configuration"
731
+ )
732
+
733
+
734
+ class PluginUninstallResponse(BaseSchema):
735
+ """Response from plugin uninstallation."""
736
+
737
+ success: bool
738
+ plugin_id: str
739
+ message: str | None = None
740
+
741
+
742
+ class PluginUpdateCheckResponse(BaseSchema):
743
+ """Response from checking for plugin updates."""
744
+
745
+ plugin_id: str
746
+ current_version: str
747
+ latest_version: str
748
+ update_available: bool
749
+ changelog: str | None = None
750
+ breaking_changes: bool = False
751
+ release_notes: str | None = None
752
+
753
+
754
+ class PluginRatingRequest(BaseSchema):
755
+ """Request to rate a plugin."""
756
+
757
+ plugin_id: str
758
+ rating: int = Field(ge=1, le=5, description="Rating from 1 to 5")
759
+ review: str | None = Field(
760
+ default=None, max_length=2000, description="Optional review text"
761
+ )
762
+
763
+
764
+ class PluginRatingResponse(BaseSchema):
765
+ """Response from rating a plugin."""
766
+
767
+ success: bool
768
+ plugin_id: str
769
+ new_average_rating: float
770
+ total_ratings: int
771
+
772
+
773
+ # =============================================================================
774
+ # Plugin Execution
775
+ # =============================================================================
776
+
777
+
778
+ class PluginExecutionContext(BaseSchema):
779
+ """Context for plugin execution."""
780
+
781
+ plugin_id: str
782
+ execution_id: str
783
+ source_id: str | None = None
784
+ validation_id: str | None = None
785
+ parameters: dict[str, Any] = Field(default_factory=dict)
786
+ sandbox_enabled: bool = True
787
+ timeout_seconds: int = 30
788
+ memory_limit_mb: int = 256
789
+
790
+
791
+ class PluginExecutionResult(BaseSchema):
792
+ """Result of plugin execution."""
793
+
794
+ execution_id: str
795
+ plugin_id: str
796
+ success: bool
797
+ result: Any = None
798
+ error: str | None = None
799
+ execution_time_ms: float = 0
800
+ memory_used_mb: float | None = None
801
+ warnings: list[str] = Field(default_factory=list)
802
+ logs: list[str] = Field(default_factory=list)
803
+
804
+
805
+ # =============================================================================
806
+ # Plugin Lifecycle & State Management
807
+ # =============================================================================
808
+
809
+
810
+ class PluginState(str, Enum):
811
+ """Plugin lifecycle states."""
812
+
813
+ DISCOVERED = "discovered"
814
+ LOADING = "loading"
815
+ LOADED = "loaded"
816
+ ACTIVATING = "activating"
817
+ ACTIVE = "active"
818
+ DEACTIVATING = "deactivating"
819
+ UNLOADING = "unloading"
820
+ UNLOADED = "unloaded"
821
+ FAILED = "failed"
822
+ RELOADING = "reloading"
823
+ UPGRADING = "upgrading"
824
+
825
+
826
+ class PluginLifecycleEvent(BaseSchema):
827
+ """A plugin lifecycle event."""
828
+
829
+ plugin_id: str
830
+ from_state: PluginState
831
+ to_state: PluginState
832
+ trigger: str
833
+ timestamp: datetime
834
+ metadata: dict[str, Any] = Field(default_factory=dict)
835
+
836
+
837
+ class PluginLifecycleResponse(BaseSchema):
838
+ """Plugin lifecycle status response."""
839
+
840
+ plugin_id: str
841
+ current_state: PluginState
842
+ can_activate: bool = False
843
+ can_deactivate: bool = False
844
+ can_reload: bool = False
845
+ can_upgrade: bool = False
846
+ recent_events: list[PluginLifecycleEvent] = Field(default_factory=list)
847
+
848
+
849
+ class PluginTransitionRequest(BaseSchema):
850
+ """Request to transition plugin state."""
851
+
852
+ target_state: PluginState
853
+ force: bool = Field(default=False, description="Force transition even if not allowed")
854
+ metadata: dict[str, Any] = Field(default_factory=dict)
855
+
856
+
857
+ class PluginTransitionResponse(BaseSchema):
858
+ """Response from plugin state transition."""
859
+
860
+ success: bool
861
+ plugin_id: str
862
+ from_state: PluginState
863
+ to_state: PluginState
864
+ message: str | None = None
865
+ error: str | None = None
866
+
867
+
868
+ # =============================================================================
869
+ # Plugin Hot Reload
870
+ # =============================================================================
871
+
872
+
873
+ class ReloadStrategy(str, Enum):
874
+ """Strategy for handling hot reloads."""
875
+
876
+ IMMEDIATE = "immediate"
877
+ DEBOUNCED = "debounced"
878
+ MANUAL = "manual"
879
+ SCHEDULED = "scheduled"
880
+
881
+
882
+ class HotReloadConfigRequest(BaseSchema):
883
+ """Request to configure hot reload for a plugin."""
884
+
885
+ strategy: ReloadStrategy = Field(default=ReloadStrategy.DEBOUNCED)
886
+ debounce_delay_ms: int = Field(default=500, ge=0, le=5000)
887
+ watch_paths: list[str] = Field(default_factory=list)
888
+ enabled: bool = True
889
+
890
+
891
+ class HotReloadStatus(BaseSchema):
892
+ """Hot reload status for a plugin."""
893
+
894
+ plugin_id: str
895
+ enabled: bool = False
896
+ watching: bool = False
897
+ strategy: ReloadStrategy = ReloadStrategy.MANUAL
898
+ has_pending_reload: bool = False
899
+ last_reload_at: datetime | None = None
900
+ last_reload_duration_ms: float | None = None
901
+
902
+
903
+ class HotReloadResult(BaseSchema):
904
+ """Result of a hot reload operation."""
905
+
906
+ success: bool
907
+ plugin_id: str
908
+ old_version: str = ""
909
+ new_version: str = ""
910
+ duration_ms: float = 0
911
+ error: str | None = None
912
+ rolled_back: bool = False
913
+ changes: list[str] = Field(default_factory=list)
914
+
915
+
916
+ # =============================================================================
917
+ # Plugin Dependencies
918
+ # =============================================================================
919
+
920
+
921
+ class DependencyType(str, Enum):
922
+ """Type of dependency relationship."""
923
+
924
+ REQUIRED = "required"
925
+ OPTIONAL = "optional"
926
+ DEV = "dev"
927
+ PEER = "peer"
928
+ CONFLICT = "conflict"
929
+
930
+
931
+ class DependencyInfo(BaseSchema):
932
+ """Detailed dependency information."""
933
+
934
+ plugin_id: str
935
+ version_constraint: str
936
+ dependency_type: DependencyType = DependencyType.REQUIRED
937
+ resolved_version: str | None = None
938
+ is_installed: bool = False
939
+ is_satisfied: bool = False
940
+
941
+
942
+ class DependencyGraphNode(BaseSchema):
943
+ """A node in the dependency graph."""
944
+
945
+ plugin_id: str
946
+ version: str
947
+ dependencies: list[DependencyInfo] = Field(default_factory=list)
948
+ dependents: list[str] = Field(default_factory=list)
949
+ depth: int = 0
950
+
951
+
952
+ class DependencyGraphResponse(BaseSchema):
953
+ """Full dependency graph for visualization."""
954
+
955
+ root_plugin_id: str
956
+ nodes: list[DependencyGraphNode] = Field(default_factory=list)
957
+ has_cycles: bool = False
958
+ cycle_path: list[str] | None = None
959
+ install_order: list[str] = Field(default_factory=list)
960
+ total_dependencies: int = 0
961
+
962
+
963
+ class DependencyResolutionRequest(BaseSchema):
964
+ """Request to resolve dependencies for installation."""
965
+
966
+ plugin_ids: list[str] = Field(description="Plugin IDs to resolve")
967
+ include_optional: bool = Field(default=False)
968
+ include_dev: bool = Field(default=False)
969
+
970
+
971
+ class DependencyResolutionResponse(BaseSchema):
972
+ """Result of dependency resolution."""
973
+
974
+ success: bool
975
+ resolved: list[DependencyInfo] = Field(default_factory=list)
976
+ unresolved: list[DependencyInfo] = Field(default_factory=list)
977
+ conflicts: list[str] = Field(default_factory=list)
978
+ install_order: list[str] = Field(default_factory=list)
979
+ error: str | None = None
980
+
981
+
982
+ # =============================================================================
983
+ # Plugin Security - Extended
984
+ # =============================================================================
985
+
986
+
987
+ class IsolationLevel(str, Enum):
988
+ """Sandbox isolation levels."""
989
+
990
+ NONE = "none"
991
+ PROCESS = "process"
992
+ CONTAINER = "container"
993
+
994
+
995
+ class SignatureAlgorithm(str, Enum):
996
+ """Supported signature algorithms."""
997
+
998
+ SHA256 = "sha256"
999
+ SHA512 = "sha512"
1000
+ HMAC_SHA256 = "hmac_sha256"
1001
+ HMAC_SHA512 = "hmac_sha512"
1002
+ RSA_SHA256 = "rsa_sha256"
1003
+ ED25519 = "ed25519"
1004
+
1005
+
1006
+ class TrustedSigner(BaseSchema):
1007
+ """A trusted signer in the trust store."""
1008
+
1009
+ signer_id: str
1010
+ name: str
1011
+ public_key: str
1012
+ algorithm: SignatureAlgorithm
1013
+ added_at: datetime
1014
+ expires_at: datetime | None = None
1015
+ is_active: bool = True
1016
+ trust_level: SecurityLevel = SecurityLevel.VERIFIED
1017
+
1018
+
1019
+ class TrustStoreResponse(BaseSchema):
1020
+ """Response containing trust store information."""
1021
+
1022
+ signers: list[TrustedSigner] = Field(default_factory=list)
1023
+ total_signers: int = 0
1024
+ last_updated: datetime | None = None
1025
+
1026
+
1027
+ class AddSignerRequest(BaseSchema):
1028
+ """Request to add a signer to the trust store."""
1029
+
1030
+ signer_id: str
1031
+ name: str
1032
+ public_key: str
1033
+ algorithm: SignatureAlgorithm = SignatureAlgorithm.ED25519
1034
+ expires_at: datetime | None = None
1035
+ trust_level: SecurityLevel = SecurityLevel.VERIFIED
1036
+
1037
+
1038
+ class SecurityPolicyPreset(str, Enum):
1039
+ """Pre-defined security policy presets."""
1040
+
1041
+ DEVELOPMENT = "development"
1042
+ TESTING = "testing"
1043
+ STANDARD = "standard"
1044
+ ENTERPRISE = "enterprise"
1045
+ STRICT = "strict"
1046
+ AIRGAPPED = "airgapped"
1047
+
1048
+
1049
+ class SecurityPolicyConfig(BaseSchema):
1050
+ """Full security policy configuration."""
1051
+
1052
+ preset: SecurityPolicyPreset = SecurityPolicyPreset.STANDARD
1053
+ isolation_level: IsolationLevel = IsolationLevel.PROCESS
1054
+ require_signature: bool = True
1055
+ min_signatures: int = Field(default=1, ge=0, le=10)
1056
+ allowed_signers: list[str] = Field(default_factory=list)
1057
+ blocked_modules: list[str] = Field(default_factory=list)
1058
+ memory_limit_mb: int = Field(default=256, ge=64, le=4096)
1059
+ cpu_time_limit_sec: int = Field(default=30, ge=1, le=600)
1060
+ network_enabled: bool = False
1061
+ filesystem_read: bool = False
1062
+ filesystem_write: bool = False
1063
+
1064
+
1065
+ class SecurityAnalysisRequest(BaseSchema):
1066
+ """Request to analyze plugin security."""
1067
+
1068
+ plugin_id: str
1069
+ code: str | None = None
1070
+ deep_analysis: bool = Field(default=False, description="Perform deep AST analysis")
1071
+
1072
+
1073
+ class CodeAnalysisResult(BaseSchema):
1074
+ """Result of code security analysis."""
1075
+
1076
+ is_safe: bool
1077
+ issues: list[str] = Field(default_factory=list)
1078
+ warnings: list[str] = Field(default_factory=list)
1079
+ blocked_constructs: list[str] = Field(default_factory=list)
1080
+ detected_imports: list[str] = Field(default_factory=list)
1081
+ detected_permissions: list[str] = Field(default_factory=list)
1082
+ complexity_score: int = Field(default=0, ge=0, le=100)
1083
+
1084
+
1085
+ class ExtendedSecurityReport(SecurityReport):
1086
+ """Extended security report with detailed analysis."""
1087
+
1088
+ code_analysis: CodeAnalysisResult | None = None
1089
+ signature_count: int = 0
1090
+ trust_level: SecurityLevel = SecurityLevel.UNVERIFIED
1091
+ can_run_in_sandbox: bool = True
1092
+ code_hash: str = ""
1093
+ recommendations: list[str] = Field(default_factory=list)
1094
+
1095
+
1096
+ class VerifySignatureRequest(BaseSchema):
1097
+ """Request to verify plugin signature."""
1098
+
1099
+ plugin_id: str
1100
+ signature: str
1101
+ content_hash: str
1102
+ algorithm: SignatureAlgorithm = SignatureAlgorithm.ED25519
1103
+
1104
+
1105
+ class VerifySignatureResponse(BaseSchema):
1106
+ """Response from signature verification."""
1107
+
1108
+ is_valid: bool
1109
+ signer_id: str | None = None
1110
+ signer_name: str | None = None
1111
+ signed_at: datetime | None = None
1112
+ error: str | None = None
1113
+
1114
+
1115
+ # =============================================================================
1116
+ # Plugin Hooks
1117
+ # =============================================================================
1118
+
1119
+
1120
+ class HookType(str, Enum):
1121
+ """Types of hooks in the plugin system."""
1122
+
1123
+ BEFORE_VALIDATION = "before_validation"
1124
+ AFTER_VALIDATION = "after_validation"
1125
+ ON_ISSUE_FOUND = "on_issue_found"
1126
+ BEFORE_PROFILE = "before_profile"
1127
+ AFTER_PROFILE = "after_profile"
1128
+ BEFORE_COMPARE = "before_compare"
1129
+ AFTER_COMPARE = "after_compare"
1130
+ ON_PLUGIN_LOAD = "on_plugin_load"
1131
+ ON_PLUGIN_UNLOAD = "on_plugin_unload"
1132
+ ON_PLUGIN_ERROR = "on_plugin_error"
1133
+ BEFORE_NOTIFICATION = "before_notification"
1134
+ AFTER_NOTIFICATION = "after_notification"
1135
+ ON_SCHEDULE_RUN = "on_schedule_run"
1136
+ ON_DATA_SOURCE_CONNECT = "on_data_source_connect"
1137
+ ON_SCHEMA_CHANGE = "on_schema_change"
1138
+ CUSTOM = "custom"
1139
+
1140
+
1141
+ class HookPriority(str, Enum):
1142
+ """Priority levels for hook execution."""
1143
+
1144
+ HIGHEST = "highest"
1145
+ HIGH = "high"
1146
+ NORMAL = "normal"
1147
+ LOW = "low"
1148
+ LOWEST = "lowest"
1149
+
1150
+
1151
+ class HookRegistration(BaseSchema):
1152
+ """A registered hook."""
1153
+
1154
+ id: str
1155
+ hook_type: HookType
1156
+ plugin_id: str
1157
+ function_name: str
1158
+ priority: HookPriority = HookPriority.NORMAL
1159
+ is_async: bool = False
1160
+ is_enabled: bool = True
1161
+ description: str | None = None
1162
+
1163
+
1164
+ class HookListResponse(BaseSchema):
1165
+ """Response listing registered hooks."""
1166
+
1167
+ hooks: list[HookRegistration] = Field(default_factory=list)
1168
+ total: int = 0
1169
+ by_type: dict[str, int] = Field(default_factory=dict)
1170
+
1171
+
1172
+ class RegisterHookRequest(BaseSchema):
1173
+ """Request to register a new hook."""
1174
+
1175
+ hook_type: HookType
1176
+ plugin_id: str
1177
+ function_name: str
1178
+ priority: HookPriority = HookPriority.NORMAL
1179
+ description: str | None = None
1180
+
1181
+
1182
+ class UnregisterHookRequest(BaseSchema):
1183
+ """Request to unregister a hook."""
1184
+
1185
+ hook_id: str
1186
+
1187
+
1188
+ class HookExecutionResult(BaseSchema):
1189
+ """Result of hook execution."""
1190
+
1191
+ hook_id: str
1192
+ plugin_id: str
1193
+ success: bool
1194
+ result: Any = None
1195
+ error: str | None = None
1196
+ execution_time_ms: float = 0
1197
+ modified_data: bool = False
1198
+
1199
+
1200
+ class HookExecutionSummary(BaseSchema):
1201
+ """Summary of all hook executions for an event."""
1202
+
1203
+ hook_type: HookType
1204
+ total_hooks: int
1205
+ successful: int
1206
+ failed: int
1207
+ total_time_ms: float
1208
+ results: list[HookExecutionResult] = Field(default_factory=list)
1209
+
1210
+
1211
+ # =============================================================================
1212
+ # Plugin Documentation
1213
+ # =============================================================================
1214
+
1215
+
1216
+ class ParameterDoc(BaseSchema):
1217
+ """Documentation for a function parameter."""
1218
+
1219
+ name: str
1220
+ type: str | None = None
1221
+ description: str | None = None
1222
+ default: str | None = None
1223
+ required: bool = True
1224
+
1225
+
1226
+ class FunctionDoc(BaseSchema):
1227
+ """Documentation for a function."""
1228
+
1229
+ name: str
1230
+ description: str | None = None
1231
+ parameters: list[ParameterDoc] = Field(default_factory=list)
1232
+ return_type: str | None = None
1233
+ return_description: str | None = None
1234
+ raises: dict[str, str] = Field(default_factory=dict)
1235
+ is_async: bool = False
1236
+ deprecated: bool = False
1237
+ examples: list[str] = Field(default_factory=list)
1238
+
1239
+
1240
+ class ClassDoc(BaseSchema):
1241
+ """Documentation for a class."""
1242
+
1243
+ name: str
1244
+ description: str | None = None
1245
+ bases: list[str] = Field(default_factory=list)
1246
+ init_params: list[ParameterDoc] = Field(default_factory=list)
1247
+ attributes: dict[str, dict[str, Any]] = Field(default_factory=dict)
1248
+ methods: list[FunctionDoc] = Field(default_factory=list)
1249
+ deprecated: bool = False
1250
+ examples: list[str] = Field(default_factory=list)
1251
+
1252
+
1253
+ class ModuleDoc(BaseSchema):
1254
+ """Documentation for a module."""
1255
+
1256
+ name: str
1257
+ description: str | None = None
1258
+ file_path: str | None = None
1259
+ classes: list[ClassDoc] = Field(default_factory=list)
1260
+ functions: list[FunctionDoc] = Field(default_factory=list)
1261
+ constants: dict[str, Any] = Field(default_factory=dict)
1262
+
1263
+
1264
+ class PluginDocumentation(BaseSchema):
1265
+ """Complete documentation for a plugin."""
1266
+
1267
+ plugin_id: str
1268
+ plugin_name: str
1269
+ version: str
1270
+ modules: list[ModuleDoc] = Field(default_factory=list)
1271
+ readme: str | None = None
1272
+ changelog: str | None = None
1273
+ examples: list[dict[str, str]] = Field(default_factory=list)
1274
+ generated_at: datetime | None = None
1275
+
1276
+
1277
+ class DocumentationRenderRequest(BaseSchema):
1278
+ """Request to render plugin documentation."""
1279
+
1280
+ plugin_id: str
1281
+ format: Literal["markdown", "html", "json"] = "markdown"
1282
+ include_private: bool = False
1283
+ include_source: bool = False
1284
+ include_examples: bool = True
1285
+
1286
+
1287
+ class DocumentationRenderResponse(BaseSchema):
1288
+ """Response with rendered documentation."""
1289
+
1290
+ plugin_id: str
1291
+ format: str
1292
+ content: str
1293
+ generation_time_ms: float = 0