Release v0.1.5

This commit is contained in:
2026-07-07 15:49:06 +02:00
commit 2defb89bc1
55 changed files with 3361 additions and 0 deletions

View File

@@ -0,0 +1,360 @@
from __future__ import annotations
from datetime import datetime
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator
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 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)
settings: dict[str, Any] = Field(default_factory=dict)
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
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)
class ModuleInstallPlanItem(BaseModel):
model_config = ConfigDict(extra="forbid")
module_id: str = Field(min_length=1, max_length=120)
action: Literal["install", "uninstall"]
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)
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 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)
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
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
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
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
class ModuleInstallerRequestCreateRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
options: ModuleInstallerRequestOptions = Field(default_factory=ModuleInstallerRequestOptions)
class ModulePackageCatalogItem(BaseModel):
module_id: str
name: str
description: str | None = None
version: str | None = None
action: Literal["install", "uninstall"] = "install"
python_package: str | None = None
python_ref: str | None = None
webui_package: str | None = None
webui_ref: str | None = None
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 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
channel: str | None = None
sequence: int | None = None
generated_at: str | None = None
expires_at: str | None = None
signed: bool = False
trusted: bool = False
key_id: str | None = None
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)
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 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)
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)