523 lines
18 KiB
Python
523 lines
18 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Any, Literal
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
|
|
from govoplan_core.api.v1.schemas import DeltaDeletedItem
|
|
|
|
|
|
RETENTION_DAY_KEYS = (
|
|
"raw_campaign_json_retention_days",
|
|
"generated_eml_retention_days",
|
|
"stored_report_detail_retention_days",
|
|
"mock_mailbox_retention_days",
|
|
"audit_detail_retention_days",
|
|
)
|
|
|
|
|
|
RETENTION_POLICY_FIELD_KEYS = (
|
|
"store_raw_campaign_json",
|
|
*RETENTION_DAY_KEYS,
|
|
"audit_detail_level",
|
|
)
|
|
|
|
|
|
def default_allow_lower_level_limits() -> dict[str, bool]:
|
|
return {key: True for key in RETENTION_POLICY_FIELD_KEYS}
|
|
|
|
|
|
def normalize_allow_lower_level_limits(value: Any, *, fill_defaults: bool) -> dict[str, bool] | None:
|
|
if value in (None, ""):
|
|
return default_allow_lower_level_limits() if fill_defaults else None
|
|
if not isinstance(value, dict):
|
|
raise ValueError("allow_lower_level_limits must be an object")
|
|
normalized = default_allow_lower_level_limits() if fill_defaults else {}
|
|
for key, allowed in value.items():
|
|
clean_key = str(key)
|
|
if clean_key not in RETENTION_POLICY_FIELD_KEYS:
|
|
raise ValueError(f"Unknown retention policy field: {clean_key}")
|
|
normalized[clean_key] = bool(allowed)
|
|
return normalized
|
|
|
|
|
|
class PrivacyRetentionPolicyItem(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
store_raw_campaign_json: bool = True
|
|
raw_campaign_json_retention_days: int | None = Field(default=None, ge=0)
|
|
generated_eml_retention_days: int | None = Field(default=None, ge=0)
|
|
stored_report_detail_retention_days: int | None = Field(default=None, ge=0)
|
|
mock_mailbox_retention_days: int | None = Field(default=None, ge=0)
|
|
audit_detail_retention_days: int | None = Field(default=None, ge=0)
|
|
audit_detail_level: Literal["full", "redacted", "minimal"] = "full"
|
|
allow_lower_level_limits: dict[str, bool] = Field(default_factory=default_allow_lower_level_limits)
|
|
|
|
@field_validator("allow_lower_level_limits", mode="before")
|
|
@classmethod
|
|
def _normalize_allow_lower_level_limits(cls, value: Any) -> Any:
|
|
return normalize_allow_lower_level_limits(value, fill_defaults=True)
|
|
|
|
|
|
class MaintenanceModeItem(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
enabled: bool = False
|
|
message: str | None = Field(default=None, max_length=500)
|
|
|
|
|
|
class LanguagePackageItem(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
code: str = Field(min_length=1, max_length=20)
|
|
label: str = Field(min_length=1, max_length=100)
|
|
native_label: str | None = Field(default=None, max_length=100)
|
|
|
|
|
|
class AdminOverviewResponse(BaseModel):
|
|
active_tenant_id: str
|
|
active_tenant_name: str
|
|
tenant_count: int | None = None
|
|
system_account_count: int | None = None
|
|
system_group_template_count: int | None = None
|
|
system_role_template_count: int | None = None
|
|
user_count: int
|
|
active_user_count: int
|
|
group_count: int
|
|
role_count: int
|
|
active_api_key_count: int
|
|
capabilities: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class SystemSettingsItem(BaseModel):
|
|
default_locale: str = "en"
|
|
allow_tenant_custom_groups: bool = True
|
|
allow_tenant_custom_roles: bool = True
|
|
allow_tenant_api_keys: bool = True
|
|
privacy_retention_policy: PrivacyRetentionPolicyItem = Field(default_factory=PrivacyRetentionPolicyItem)
|
|
maintenance_mode: MaintenanceModeItem = Field(default_factory=MaintenanceModeItem)
|
|
available_languages: list[LanguagePackageItem] = Field(default_factory=list)
|
|
enabled_language_codes: list[str] = Field(default_factory=list)
|
|
settings: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
class SystemSettingsDeltaResponse(BaseModel):
|
|
item: SystemSettingsItem | None = None
|
|
sections: dict[str, Any] = Field(default_factory=dict)
|
|
changed_sections: list[str] = Field(default_factory=list)
|
|
deleted: list[DeltaDeletedItem] = Field(default_factory=list)
|
|
watermark: str | None = None
|
|
has_more: bool = False
|
|
full: bool = False
|
|
|
|
|
|
class SystemSettingsUpdateRequest(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
default_locale: str = Field(min_length=1, max_length=20)
|
|
allow_tenant_custom_groups: bool
|
|
allow_tenant_custom_roles: bool
|
|
allow_tenant_api_keys: bool
|
|
privacy_retention_policy: PrivacyRetentionPolicyItem | None = None
|
|
maintenance_mode: MaintenanceModeItem | None = None
|
|
available_languages: list[LanguagePackageItem] | None = None
|
|
enabled_language_codes: list[str] | None = None
|
|
change_request_id: str | None = None
|
|
|
|
|
|
class ModuleCatalogItem(BaseModel):
|
|
id: str
|
|
name: str
|
|
version: str
|
|
installed: bool = True
|
|
current_enabled: bool = False
|
|
desired_enabled: bool = False
|
|
protected: bool = False
|
|
restart_required: bool = False
|
|
dependencies: list[str] = Field(default_factory=list)
|
|
optional_dependencies: list[str] = Field(default_factory=list)
|
|
dependents: list[str] = Field(default_factory=list)
|
|
permissions_count: int = 0
|
|
role_templates_count: int = 0
|
|
route_contributed: bool = False
|
|
nav_count: int = 0
|
|
frontend_package: str | None = None
|
|
migration_module_id: str | None = None
|
|
migration_script_location: str | None = None
|
|
runtime_toggle_supported: bool = False
|
|
install_uninstall_supported: bool = False
|
|
|
|
|
|
class ModuleCatalogResponse(BaseModel):
|
|
modules: list[ModuleCatalogItem]
|
|
current_enabled: list[str]
|
|
desired_enabled: list[str]
|
|
configured_enabled: list[str]
|
|
protected_modules: list[str]
|
|
restart_required: bool = False
|
|
runtime_toggle_supported: bool = False
|
|
install_uninstall_supported: bool = False
|
|
install_plan_supported: bool = False
|
|
package_mutation_supported: bool = False
|
|
maintenance_mode: MaintenanceModeItem = Field(default_factory=MaintenanceModeItem)
|
|
notes: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class ModuleStateUpdateRequest(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
enabled_modules: list[str] = Field(default_factory=list)
|
|
change_request_id: str | None = None
|
|
|
|
|
|
class ModuleInstallPlanItem(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
module_id: str = Field(min_length=1, max_length=120)
|
|
action: Literal["install", "update", "uninstall"]
|
|
source: Literal["manual", "catalog"] = "manual"
|
|
catalog: dict[str, Any] | None = None
|
|
python_package: str | None = Field(default=None, max_length=200)
|
|
python_ref: str | None = Field(default=None, max_length=1000)
|
|
webui_package: str | None = Field(default=None, max_length=200)
|
|
webui_ref: str | None = Field(default=None, max_length=1000)
|
|
data_safety_acknowledged: bool = False
|
|
destroy_data: bool = False
|
|
status: Literal["planned", "applied", "blocked"] = "planned"
|
|
notes: str | None = Field(default=None, max_length=1000)
|
|
|
|
|
|
class ModuleInstallPreflightIssue(BaseModel):
|
|
severity: Literal["blocker", "warning", "info"]
|
|
code: str
|
|
message: str
|
|
module_id: str | None = None
|
|
|
|
|
|
class ModuleInstallChecklistItem(BaseModel):
|
|
id: str
|
|
label: str
|
|
status: Literal["done", "pending", "blocked", "warning"]
|
|
detail: str | None = None
|
|
|
|
|
|
class ModuleInstallTargetItem(BaseModel):
|
|
module_id: str
|
|
action: Literal["install", "update", "uninstall"]
|
|
source: Literal["manual", "catalog"]
|
|
current_version: str | None = None
|
|
target_version: str | None = None
|
|
python_package: str | None = None
|
|
python_ref: str | None = None
|
|
webui_package: str | None = None
|
|
webui_ref: str | None = None
|
|
migration_safety: Literal["automatic", "requires_review", "forward_only", "destructive"] = "automatic"
|
|
migration_notes: str | None = None
|
|
current_version_min: str | None = None
|
|
current_version_max_exclusive: str | None = None
|
|
bridge_release: bool = False
|
|
bridge_notes: str | None = None
|
|
allow_downgrade: bool = False
|
|
allow_same_version: bool = False
|
|
recovery_tested: bool = False
|
|
recovery_notes: str | None = None
|
|
data_safety_acknowledged: bool = False
|
|
|
|
|
|
class ModuleMigrationPlanStep(BaseModel):
|
|
module_id: str
|
|
action: Literal["install", "update", "uninstall"]
|
|
phase: Literal["upgrade", "retirement"]
|
|
source: Literal["manifest", "catalog", "pending"]
|
|
has_migration_metadata: bool = False
|
|
metadata_pending: bool = False
|
|
migration_safety: Literal["automatic", "requires_review", "forward_only", "destructive"] = "automatic"
|
|
current_version: str | None = None
|
|
target_version: str | None = None
|
|
reason: str | None = None
|
|
|
|
|
|
class ModuleMigrationTaskPlanItem(BaseModel):
|
|
module_id: str
|
|
task_id: str
|
|
phase: Literal["pre_migration_check", "pre_migration_prepare", "post_migration_backfill", "post_migration_verify"]
|
|
summary: str
|
|
task_version: str = "1"
|
|
safety: Literal["automatic", "requires_review", "forward_only", "destructive"] = "automatic"
|
|
idempotent: bool = True
|
|
timeout_seconds: int | None = None
|
|
source: Literal["manifest", "catalog", "pending"] = "manifest"
|
|
has_executor: bool = False
|
|
metadata_pending: bool = False
|
|
|
|
|
|
class ModuleMigrationExecutionPlan(BaseModel):
|
|
enabled_modules: list[str] = Field(default_factory=list)
|
|
requires_database_migration: bool = False
|
|
steps: list[ModuleMigrationPlanStep] = Field(default_factory=list)
|
|
tasks: list[ModuleMigrationTaskPlanItem] = Field(default_factory=list)
|
|
|
|
|
|
class ModuleInstallPreflightResponse(BaseModel):
|
|
allowed: bool
|
|
maintenance_mode: bool
|
|
frontend_rebuild_required: bool
|
|
restart_required: bool
|
|
commands: list[str] = Field(default_factory=list)
|
|
rollback_commands: list[str] = Field(default_factory=list)
|
|
issues: list[ModuleInstallPreflightIssue] = Field(default_factory=list)
|
|
checklist: list[ModuleInstallChecklistItem] = Field(default_factory=list)
|
|
target_plan: list[ModuleInstallTargetItem] = Field(default_factory=list)
|
|
migration_plan: ModuleMigrationExecutionPlan = Field(default_factory=ModuleMigrationExecutionPlan)
|
|
|
|
|
|
class ModuleInstallPlanResponse(BaseModel):
|
|
items: list[ModuleInstallPlanItem] = Field(default_factory=list)
|
|
updated_at: str | None = None
|
|
commands: list[str] = Field(default_factory=list)
|
|
maintenance_mode: MaintenanceModeItem = Field(default_factory=MaintenanceModeItem)
|
|
preflight: ModuleInstallPreflightResponse | None = None
|
|
notes: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class ModuleInstallerLockStatus(BaseModel):
|
|
model_config = ConfigDict(extra="allow")
|
|
|
|
locked: bool = False
|
|
path: str
|
|
|
|
|
|
class ModuleInstallerDaemonStatus(BaseModel):
|
|
model_config = ConfigDict(extra="allow")
|
|
|
|
running: bool = False
|
|
status: str = "unknown"
|
|
path: str
|
|
|
|
|
|
class ModuleInstallerRunSummary(BaseModel):
|
|
run_id: str
|
|
status: str
|
|
started_at: str | None = None
|
|
finished_at: str | None = None
|
|
request_id: str | None = None
|
|
requested_by: str | None = None
|
|
trace: dict[str, Any] | None = None
|
|
rollback_status: str | None = None
|
|
supervisor_status: str | None = None
|
|
error: str | None = None
|
|
record_path: str
|
|
commands_count: int = 0
|
|
planned_modules: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class ModuleInstallerRunListResponse(BaseModel):
|
|
runs: list[ModuleInstallerRunSummary] = Field(default_factory=list)
|
|
lock: ModuleInstallerLockStatus
|
|
cursor: str | None = None
|
|
next_cursor: str | None = None
|
|
full: bool = False
|
|
|
|
|
|
class ModuleInstallerRequestOptions(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
build_webui: bool = False
|
|
migrate_database: bool = True
|
|
webui_root: str | None = Field(default=None, max_length=1000)
|
|
npm_bin: str | None = Field(default=None, max_length=500)
|
|
database_backup_command: str | None = Field(default=None, max_length=4000)
|
|
database_restore_command: str | None = Field(default=None, max_length=4000)
|
|
database_restore_check_command: str | None = Field(default=None, max_length=4000)
|
|
activate_installed_modules: bool = True
|
|
remove_uninstalled_modules_from_desired: bool = True
|
|
restart_commands: list[str] = Field(default_factory=list)
|
|
health_urls: list[str] = Field(default_factory=list)
|
|
health_timeout_seconds: float = Field(default=60.0, ge=1, le=3600)
|
|
health_interval_seconds: float = Field(default=2.0, ge=0.2, le=120)
|
|
|
|
|
|
class ModuleInstallerRequestItem(BaseModel):
|
|
model_config = ConfigDict(extra="allow")
|
|
|
|
request_id: str
|
|
status: str
|
|
created_at: str
|
|
requested_by: str | None = None
|
|
started_at: str | None = None
|
|
finished_at: str | None = None
|
|
cancelled_at: str | None = None
|
|
cancelled_by: str | None = None
|
|
retry_of: str | None = None
|
|
trace: dict[str, Any] | None = None
|
|
error: str | None = None
|
|
record_path: str | None = None
|
|
options: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
class ModuleInstallerRequestListResponse(BaseModel):
|
|
requests: list[ModuleInstallerRequestItem] = Field(default_factory=list)
|
|
daemon: ModuleInstallerDaemonStatus
|
|
cursor: str | None = None
|
|
next_cursor: str | None = None
|
|
full: bool = False
|
|
|
|
|
|
class ModuleInstallerRequestCreateRequest(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
options: ModuleInstallerRequestOptions = Field(default_factory=ModuleInstallerRequestOptions)
|
|
|
|
|
|
class ModuleInterfaceProviderItem(BaseModel):
|
|
name: str
|
|
version: str
|
|
|
|
|
|
class ModuleInterfaceRequirementItem(BaseModel):
|
|
name: str
|
|
version_min: str | None = None
|
|
version_max_exclusive: str | None = None
|
|
optional: bool = False
|
|
|
|
|
|
class ModulePackageCatalogItem(BaseModel):
|
|
module_id: str
|
|
name: str
|
|
description: str | None = None
|
|
version: str | None = None
|
|
action: Literal["install", "update", "uninstall"] = "install"
|
|
dependencies: list[str] = Field(default_factory=list)
|
|
optional_dependencies: list[str] = Field(default_factory=list)
|
|
migration_safety: Literal["automatic", "requires_review", "forward_only", "destructive"] = "automatic"
|
|
migration_notes: str | None = None
|
|
migration_after: list[str] = Field(default_factory=list)
|
|
migration_before: list[str] = Field(default_factory=list)
|
|
current_version_min: str | None = None
|
|
current_version_max_exclusive: str | None = None
|
|
bridge_release: bool = False
|
|
bridge_notes: str | None = None
|
|
allow_downgrade: bool = False
|
|
allow_same_version: bool = False
|
|
recovery_tested: bool = False
|
|
recovery_notes: str | None = None
|
|
python_package: str | None = None
|
|
python_ref: str | None = None
|
|
webui_package: str | None = None
|
|
webui_ref: str | None = None
|
|
provides_interfaces: list[ModuleInterfaceProviderItem] = Field(default_factory=list)
|
|
requires_interfaces: list[ModuleInterfaceRequirementItem] = Field(default_factory=list)
|
|
license_features: list[str] = Field(default_factory=list)
|
|
license_allowed: bool = True
|
|
license_enforced: bool = False
|
|
license_missing_features: list[str] = Field(default_factory=list)
|
|
license_reason: str | None = None
|
|
notes: str | None = None
|
|
tags: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class ModuleLicenseDiagnostics(BaseModel):
|
|
configured: bool = False
|
|
valid: bool = True
|
|
path: str | None = None
|
|
license_id: str | None = None
|
|
subject: str | None = None
|
|
features: list[str] = Field(default_factory=list)
|
|
valid_from: str | None = None
|
|
valid_until: str | None = None
|
|
signed: bool = False
|
|
trusted: bool = False
|
|
key_id: str | None = None
|
|
enforced: bool = False
|
|
allowed: bool = True
|
|
required_features: list[str] = Field(default_factory=list)
|
|
missing_features: list[str] = Field(default_factory=list)
|
|
expires_in_days: int | None = None
|
|
reason: str | None = None
|
|
guidance: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class ModulePackageCatalogResponse(BaseModel):
|
|
modules: list[ModulePackageCatalogItem] = Field(default_factory=list)
|
|
configured: bool = False
|
|
valid: bool = True
|
|
path: str | None = None
|
|
source: str | None = None
|
|
source_type: str | None = None
|
|
cache_used: bool = False
|
|
cache_path: str | None = None
|
|
channel: str | None = None
|
|
sequence: int | None = None
|
|
generated_at: str | None = None
|
|
not_before: str | None = None
|
|
expires_at: str | None = None
|
|
signed: bool = False
|
|
trusted: bool = False
|
|
key_id: str | None = None
|
|
license: ModuleLicenseDiagnostics = Field(default_factory=ModuleLicenseDiagnostics)
|
|
warnings: list[str] = Field(default_factory=list)
|
|
error: str | None = None
|
|
|
|
|
|
class ModuleInstallPlanUpdateRequest(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
items: list[ModuleInstallPlanItem] = Field(default_factory=list)
|
|
change_request_id: str | None = None
|
|
|
|
|
|
class GovernanceAssignment(BaseModel):
|
|
tenant_id: str
|
|
mode: Literal["available", "required"] = "available"
|
|
|
|
|
|
class GovernanceTemplateItem(BaseModel):
|
|
id: str
|
|
kind: Literal["group", "role"]
|
|
slug: str
|
|
name: str
|
|
description: str | None = None
|
|
permissions: list[str] = Field(default_factory=list)
|
|
effective_permission_count: int = 0
|
|
is_active: bool = True
|
|
assignments: list[GovernanceAssignment] = Field(default_factory=list)
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
class GovernanceTemplateListResponse(BaseModel):
|
|
templates: list[GovernanceTemplateItem]
|
|
|
|
|
|
class GovernanceTemplateListDeltaResponse(BaseModel):
|
|
templates: list[GovernanceTemplateItem] = Field(default_factory=list)
|
|
deleted: list[DeltaDeletedItem] = Field(default_factory=list)
|
|
watermark: str | None = None
|
|
has_more: bool = False
|
|
full: bool = False
|
|
|
|
|
|
class GovernanceTemplateCreateRequest(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
kind: Literal["group", "role"]
|
|
slug: str
|
|
name: str = Field(min_length=1, max_length=255)
|
|
description: str | None = None
|
|
permissions: list[str] = Field(default_factory=list)
|
|
is_active: bool = True
|
|
assignments: list[GovernanceAssignment] = Field(default_factory=list)
|
|
change_request_id: str | None = None
|
|
|
|
|
|
class GovernanceTemplateUpdateRequest(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
name: str = Field(min_length=1, max_length=255)
|
|
description: str | None = None
|
|
permissions: list[str] = Field(default_factory=list)
|
|
is_active: bool = True
|
|
assignments: list[GovernanceAssignment] = Field(default_factory=list)
|
|
change_request_id: str | None = None
|