You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
orquestrador/admin_app/api/schemas.py

1124 lines
31 KiB
Python

from datetime import datetime
from pydantic import BaseModel, Field, field_validator
from admin_app.core import AdminCredentialStrategy
from shared.contracts import (
AdminPermission,
OperationalConsistencyModel,
OperationalDataDomain,
OperationalDataSensitivity,
OperationalFreshnessTarget,
OperationalQuerySurface,
OperationalReadGranularity,
OperationalReadModel,
OperationalStorageShape,
OperationalSyncStrategy,
ServiceName,
StaffRole,
ToolLifecycleStatus,
ToolParameterType,
)
# Contratos de request/response.
class AdminRootResponse(BaseModel):
service: str
status: str
message: str
environment: str
class AdminHealthResponse(BaseModel):
service: str
status: str
version: str
class AdminSystemInfoResponse(BaseModel):
service: str
app_name: str
environment: str
version: str
api_prefix: str
debug: bool
class AdminAuthenticatedStaffResponse(BaseModel):
id: int
email: str
display_name: str
role: StaffRole
is_active: bool
class AdminCurrentAccessResponse(BaseModel):
service: str
staff_account: AdminAuthenticatedStaffResponse
permissions: list[str]
class AdminCapabilityResponse(BaseModel):
service: str
action: str
allowed: bool
role: StaffRole
class AdminAuditEntryResponse(BaseModel):
id: int
actor_staff_account_id: int | None
event_type: str
resource_type: str
resource_id: str | None
outcome: str
message: str | None
payload_json: dict | None
ip_address: str | None
user_agent: str | None
created_at: datetime
class AdminAuditListResponse(BaseModel):
service: str
events: list[AdminAuditEntryResponse]
class AdminRuntimeApplicationConfigurationResponse(BaseModel):
app_name: str
environment: str
version: str
api_prefix: str
debug: bool
class AdminRuntimeDatabaseConfigurationResponse(BaseModel):
host: str
port: int
name: str
cloud_sql_configured: bool
class AdminPanelSessionConfigurationResponse(BaseModel):
access_cookie_name: str
refresh_cookie_name: str
cookie_path: str
same_site: str
secure_cookies: bool
class AdminSystemRuntimeConfigurationPayload(BaseModel):
application: AdminRuntimeApplicationConfigurationResponse
database: AdminRuntimeDatabaseConfigurationResponse
panel_session: AdminPanelSessionConfigurationResponse
class AdminConfigurationSourceResponse(BaseModel):
key: str
source: str
mutable: bool
description: str
class AdminFunctionalConfigurationFieldResponse(BaseModel):
name: str
description: str
writable: bool
secret: bool
class AdminFunctionalConfigurationContractResponse(BaseModel):
config_key: str
domain: str
description: str
source: str
read_permission: str
write_permission: str | None = None
mutability: str
propagation: str
affects_product_runtime: bool
direct_product_write_allowed: bool
fields: list[AdminFunctionalConfigurationFieldResponse]
class AdminModelRuntimeProfileResponse(BaseModel):
runtime_target: str
config_key: str
catalog_runtime_target: str
purpose: str
consumed_by_service: str
description: str
read_permission: str
write_permission: str
published_independently: bool
rollback_independently: bool
cross_target_propagation_allowed: bool
affects_customer_response: bool
can_generate_code: bool
class AdminSystemModelRuntimeSeparationPayload(BaseModel):
runtime_profiles: list[AdminModelRuntimeProfileResponse]
separation_rules: list[str]
atendimento_runtime_configuration: AdminFunctionalConfigurationContractResponse
tool_generation_runtime_configuration: AdminFunctionalConfigurationContractResponse
bot_governed_parent_config_keys: list[str]
class AdminSystemRuntimeConfigurationResponse(BaseModel):
service: str
runtime: AdminSystemRuntimeConfigurationPayload
class AdminSystemSecurityConfigurationResponse(BaseModel):
service: str
security: AdminCredentialStrategy
class AdminSystemWriteGovernancePayload(BaseModel):
mode: str
allowed_direct_write_tables: list[str]
blocked_operational_dataset_keys: list[str]
blocked_product_source_tables: list[str]
governed_configuration_keys: list[str]
enforcement_points: list[str]
governance_rules: list[str]
class AdminSystemConfigurationResponse(BaseModel):
service: str
runtime: AdminSystemRuntimeConfigurationPayload
security: AdminCredentialStrategy
model_runtimes: AdminSystemModelRuntimeSeparationPayload
write_governance: AdminSystemWriteGovernancePayload
sources: list[AdminConfigurationSourceResponse]
class AdminSystemModelRuntimeSeparationResponse(BaseModel):
service: str
model_runtimes: AdminSystemModelRuntimeSeparationPayload
class AdminSystemWriteGovernanceResponse(BaseModel):
service: str
write_governance: AdminSystemWriteGovernancePayload
class AdminBotGovernedSettingResponse(BaseModel):
setting_key: str
parent_config_key: str
field_name: str
area: str
description: str
read_permission: str
write_permission: str
mutability: str
versioned_publication_required: bool
direct_product_write_allowed: bool
class AdminSystemFunctionalConfigurationCatalogResponse(BaseModel):
service: str
mode: str
configurations: list[AdminFunctionalConfigurationContractResponse]
bot_governed_parent_config_keys: list[str]
next_steps: list[str]
class AdminSystemFunctionalConfigurationDetailResponse(BaseModel):
service: str
configuration: AdminFunctionalConfigurationContractResponse
linked_bot_settings: list[AdminBotGovernedSettingResponse]
related_runtime_profile: AdminModelRuntimeProfileResponse | None = None
managed_by_bot_governance: bool
class AdminSystemBotGovernedConfigurationResponse(BaseModel):
service: str
parent_config_keys: list[str]
settings: list[AdminBotGovernedSettingResponse]
class AdminReportMetricResponse(BaseModel):
key: str
label: str
value: str
description: str
class AdminReportFamilyResponse(BaseModel):
key: str
label: str
description: str
dataset_keys: list[str]
class AdminReportMaterializationResponse(BaseModel):
report_read_model: OperationalReadModel
consistency_model: OperationalConsistencyModel
sync_strategy: OperationalSyncStrategy
storage_shape: OperationalStorageShape
query_surface: OperationalQuerySurface
uses_product_replica: bool
direct_product_query_allowed: bool
refresh_behavior: str
class AdminReportFieldResponse(BaseModel):
name: str
description: str
sensitivity: OperationalDataSensitivity
class AdminReportDatasetSummaryResponse(BaseModel):
dataset_key: str
domain: OperationalDataDomain
description: str
source_table: str
freshness_target: OperationalFreshnessTarget
allowed_granularities: list[OperationalReadGranularity]
allowed_field_count: int
blocked_field_count: int
write_allowed: bool
materialization_status: str
last_consolidated_at: datetime | None = None
source_watermark: str | None = None
class AdminReportDatasetDetailResponse(BaseModel):
dataset_key: str
domain: OperationalDataDomain
description: str
source_table: str
read_permission: AdminPermission
report_read_model: OperationalReadModel
consistency_model: OperationalConsistencyModel
sync_strategy: OperationalSyncStrategy
storage_shape: OperationalStorageShape
query_surface: OperationalQuerySurface
uses_product_replica: bool
direct_product_query_allowed: bool
freshness_target: OperationalFreshnessTarget
allowed_granularities: list[OperationalReadGranularity]
write_allowed: bool
materialization_status: str
last_consolidated_at: datetime | None = None
source_watermark: str | None = None
allowed_fields: list[AdminReportFieldResponse]
blocked_fields: list[AdminReportFieldResponse]
class AdminReportOverviewResponse(BaseModel):
service: str
mode: str
metrics: list[AdminReportMetricResponse]
materialization: AdminReportMaterializationResponse
report_families: list[AdminReportFamilyResponse]
next_steps: list[str]
class AdminReportDatasetListResponse(BaseModel):
service: str
source: str
materialization: AdminReportMaterializationResponse
datasets: list[AdminReportDatasetSummaryResponse]
class AdminReportDatasetResponse(BaseModel):
service: str
source: str
materialization: AdminReportMaterializationResponse
dataset: AdminReportDatasetDetailResponse
class AdminSalesReportMetricDefinitionResponse(BaseModel):
key: str
label: str
aggregation: str
description: str
class AdminSalesReportDimensionResponse(BaseModel):
field_name: str
label: str
description: str
default_group_by: bool = False
class AdminSalesReportFilterResponse(BaseModel):
field_name: str
label: str
filter_type: str
description: str
required: bool = False
class AdminSalesReportDefinitionSummaryResponse(BaseModel):
report_key: str
label: str
description: str
dataset_key: str
default_time_field: str
default_granularity: OperationalReadGranularity
supported_metric_keys: list[str]
supported_dimension_fields: list[str]
materialization_status: str
last_consolidated_at: datetime | None = None
source_watermark: str | None = None
class AdminSalesReportDefinitionDetailResponse(BaseModel):
report_key: str
label: str
description: str
dataset_key: str
default_time_field: str
default_granularity: OperationalReadGranularity
metrics: list[AdminSalesReportMetricDefinitionResponse]
dimensions: list[AdminSalesReportDimensionResponse]
filters: list[AdminSalesReportFilterResponse]
dataset: AdminReportDatasetDetailResponse
class AdminSalesReportOverviewResponse(BaseModel):
service: str
domain: OperationalDataDomain
mode: str
source_dataset_keys: list[str]
metrics: list[AdminReportMetricResponse]
materialization: AdminReportMaterializationResponse
reports: list[AdminSalesReportDefinitionSummaryResponse]
next_steps: list[str]
class AdminSalesReportCatalogResponse(BaseModel):
service: str
domain: OperationalDataDomain
source: str
materialization: AdminReportMaterializationResponse
reports: list[AdminSalesReportDefinitionSummaryResponse]
class AdminSalesReportResponse(BaseModel):
service: str
domain: OperationalDataDomain
source: str
materialization: AdminReportMaterializationResponse
report: AdminSalesReportDefinitionDetailResponse
class AdminRevenueReportDefinitionSummaryResponse(BaseModel):
report_key: str
label: str
description: str
dataset_key: str
default_time_field: str
default_granularity: OperationalReadGranularity
supported_metric_keys: list[str]
supported_dimension_fields: list[str]
materialization_status: str
last_consolidated_at: datetime | None = None
source_watermark: str | None = None
class AdminRevenueReportDefinitionDetailResponse(BaseModel):
report_key: str
label: str
description: str
dataset_key: str
default_time_field: str
default_granularity: OperationalReadGranularity
metrics: list[AdminSalesReportMetricDefinitionResponse]
dimensions: list[AdminSalesReportDimensionResponse]
filters: list[AdminSalesReportFilterResponse]
dataset: AdminReportDatasetDetailResponse
class AdminRevenueReportOverviewResponse(BaseModel):
service: str
area: str
source_domain: OperationalDataDomain
mode: str
source_dataset_keys: list[str]
metrics: list[AdminReportMetricResponse]
materialization: AdminReportMaterializationResponse
reports: list[AdminRevenueReportDefinitionSummaryResponse]
next_steps: list[str]
class AdminRevenueReportCatalogResponse(BaseModel):
service: str
area: str
source_domain: OperationalDataDomain
source: str
materialization: AdminReportMaterializationResponse
reports: list[AdminRevenueReportDefinitionSummaryResponse]
class AdminRevenueReportResponse(BaseModel):
service: str
area: str
source_domain: OperationalDataDomain
source: str
materialization: AdminReportMaterializationResponse
report: AdminRevenueReportDefinitionDetailResponse
class AdminRentalReportDefinitionSummaryResponse(BaseModel):
report_key: str
label: str
description: str
dataset_key: str
default_time_field: str
default_granularity: OperationalReadGranularity
supported_metric_keys: list[str]
supported_dimension_fields: list[str]
materialization_status: str
last_consolidated_at: datetime | None = None
source_watermark: str | None = None
class AdminRentalReportDefinitionDetailResponse(BaseModel):
report_key: str
label: str
description: str
dataset_key: str
default_time_field: str
default_granularity: OperationalReadGranularity
metrics: list[AdminSalesReportMetricDefinitionResponse]
dimensions: list[AdminSalesReportDimensionResponse]
filters: list[AdminSalesReportFilterResponse]
dataset: AdminReportDatasetDetailResponse
class AdminRentalReportOverviewResponse(BaseModel):
service: str
area: str
source_domain: OperationalDataDomain
mode: str
source_dataset_keys: list[str]
metrics: list[AdminReportMetricResponse]
materialization: AdminReportMaterializationResponse
reports: list[AdminRentalReportDefinitionSummaryResponse]
next_steps: list[str]
class AdminRentalReportCatalogResponse(BaseModel):
service: str
area: str
source_domain: OperationalDataDomain
source: str
materialization: AdminReportMaterializationResponse
reports: list[AdminRentalReportDefinitionSummaryResponse]
class AdminRentalReportResponse(BaseModel):
service: str
area: str
source_domain: OperationalDataDomain
source: str
materialization: AdminReportMaterializationResponse
report: AdminRentalReportDefinitionDetailResponse
class AdminBotFlowReportDefinitionSummaryResponse(BaseModel):
report_key: str
label: str
description: str
dataset_key: str
default_time_field: str
default_granularity: OperationalReadGranularity
supported_metric_keys: list[str]
supported_dimension_fields: list[str]
materialization_status: str
last_consolidated_at: datetime | None = None
source_watermark: str | None = None
class AdminBotFlowReportDefinitionDetailResponse(BaseModel):
report_key: str
label: str
description: str
dataset_key: str
default_time_field: str
default_granularity: OperationalReadGranularity
metrics: list[AdminSalesReportMetricDefinitionResponse]
dimensions: list[AdminSalesReportDimensionResponse]
filters: list[AdminSalesReportFilterResponse]
dataset: AdminReportDatasetDetailResponse
class AdminBotFlowReportOverviewResponse(BaseModel):
service: str
area: str
source_domain: OperationalDataDomain
mode: str
source_dataset_keys: list[str]
metrics: list[AdminReportMetricResponse]
materialization: AdminReportMaterializationResponse
reports: list[AdminBotFlowReportDefinitionSummaryResponse]
next_steps: list[str]
class AdminBotFlowReportCatalogResponse(BaseModel):
service: str
area: str
source_domain: OperationalDataDomain
source: str
materialization: AdminReportMaterializationResponse
reports: list[AdminBotFlowReportDefinitionSummaryResponse]
class AdminBotFlowReportResponse(BaseModel):
service: str
area: str
source_domain: OperationalDataDomain
source: str
materialization: AdminReportMaterializationResponse
report: AdminBotFlowReportDefinitionDetailResponse
class AdminConversationTelemetryReportDefinitionSummaryResponse(BaseModel):
report_key: str
label: str
description: str
dataset_key: str
default_time_field: str
default_granularity: OperationalReadGranularity
supported_metric_keys: list[str]
supported_dimension_fields: list[str]
materialization_status: str
last_consolidated_at: datetime | None = None
source_watermark: str | None = None
class AdminConversationTelemetryReportDefinitionDetailResponse(BaseModel):
report_key: str
label: str
description: str
dataset_key: str
default_time_field: str
default_granularity: OperationalReadGranularity
metrics: list[AdminSalesReportMetricDefinitionResponse]
dimensions: list[AdminSalesReportDimensionResponse]
filters: list[AdminSalesReportFilterResponse]
dataset: AdminReportDatasetDetailResponse
class AdminConversationTelemetryReportOverviewResponse(BaseModel):
service: str
area: str
source_domain: OperationalDataDomain
mode: str
source_dataset_keys: list[str]
metrics: list[AdminReportMetricResponse]
materialization: AdminReportMaterializationResponse
reports: list[AdminConversationTelemetryReportDefinitionSummaryResponse]
next_steps: list[str]
class AdminConversationTelemetryReportCatalogResponse(BaseModel):
service: str
area: str
source_domain: OperationalDataDomain
source: str
materialization: AdminReportMaterializationResponse
reports: list[AdminConversationTelemetryReportDefinitionSummaryResponse]
class AdminConversationTelemetryReportResponse(BaseModel):
service: str
area: str
source_domain: OperationalDataDomain
source: str
materialization: AdminReportMaterializationResponse
report: AdminConversationTelemetryReportDefinitionDetailResponse
class AdminLoginRequest(BaseModel):
email: str
password: str = Field(min_length=1)
@field_validator("email")
@classmethod
def validate_email(cls, value: str) -> str:
normalized = value.strip().lower()
if "@" not in normalized or normalized.startswith("@") or normalized.endswith("@"):
raise ValueError("email must be a valid administrative login")
return normalized
class AdminRefreshTokenRequest(BaseModel):
refresh_token: str = Field(min_length=1)
class AdminSessionResponse(BaseModel):
session_id: int
access_token: str
refresh_token: str
token_type: str
expires_in_seconds: int
staff_account: AdminAuthenticatedStaffResponse
class AdminLogoutResponse(BaseModel):
service: str
status: str
message: str
session_id: int
class AdminPanelWebSessionResponse(BaseModel):
service: str
status: str
message: str
session_id: int
expires_in_seconds: int
staff_account: AdminAuthenticatedStaffResponse
redirect_to: str | None = None
class AdminPanelLogoutResponse(BaseModel):
service: str
status: str
message: str
session_id: int | None
redirect_to: str
class AdminToolManagementMetricResponse(BaseModel):
key: str
label: str
value: str
description: str
class AdminToolLifecycleStageResponse(BaseModel):
code: ToolLifecycleStatus
label: str
description: str
order: int = Field(ge=1)
terminal: bool = False
class AdminToolParameterTypeResponse(BaseModel):
code: ToolParameterType
label: str
description: str
class AdminToolManagementActionResponse(BaseModel):
key: str
label: str
href: str
required_permission: AdminPermission
description: str
class AdminToolOverviewResponse(BaseModel):
service: str
mode: str
metrics: list[AdminToolManagementMetricResponse]
workflow: list[AdminToolLifecycleStageResponse]
actions: list[AdminToolManagementActionResponse]
next_steps: list[str]
class AdminToolContractsResponse(BaseModel):
service: str
publication_source_service: ServiceName
publication_target_service: ServiceName
lifecycle_statuses: list[AdminToolLifecycleStageResponse]
parameter_types: list[AdminToolParameterTypeResponse]
publication_fields: list[str]
published_tool_fields: list[str]
class AdminToolDraftSummaryResponse(BaseModel):
draft_id: str
tool_name: str
display_name: str
status: ToolLifecycleStatus
summary: str
current_version_number: int = Field(ge=1)
version_count: int = Field(ge=1)
owner_name: str | None = None
updated_at: datetime | None = None
class AdminToolDraftListResponse(BaseModel):
service: str
storage_status: str
message: str
drafts: list[AdminToolDraftSummaryResponse]
supported_statuses: list[ToolLifecycleStatus]
class AdminToolReviewQueueEntryResponse(BaseModel):
entry_id: str
version_id: str
version_number: int = Field(ge=1)
tool_name: str
display_name: str
status: ToolLifecycleStatus
gate: str
summary: str
owner_name: str | None = None
automated_validation_status: str | None = None
automated_validation_summary: str | None = None
queued_at: datetime | None = None
class AdminToolReviewQueueResponse(BaseModel):
service: str
queue_mode: str
message: str
items: list[AdminToolReviewQueueEntryResponse]
supported_statuses: list[ToolLifecycleStatus]
class AdminToolReviewDecisionRequest(BaseModel):
decision_notes: str = Field(min_length=12, max_length=2000)
reviewed_generated_code: bool = False
@field_validator("decision_notes")
@classmethod
def normalize_decision_notes(cls, value: str) -> str:
return value.strip()
class AdminToolGovernanceDecisionRequest(BaseModel):
decision_notes: str = Field(min_length=12, max_length=2000)
@field_validator("decision_notes")
@classmethod
def normalize_decision_notes(cls, value: str) -> str:
return value.strip()
class AdminToolOptionalGovernanceDecisionRequest(BaseModel):
decision_notes: str | None = Field(default=None, max_length=2000)
@field_validator("decision_notes", mode="before")
@classmethod
def normalize_decision_notes(cls, value: str | None) -> str | None:
if value is None:
return None
normalized = str(value).strip()
return normalized or None
class AdminToolReviewHumanGateResponse(BaseModel):
current_gate: str
authorize_generation_action_available: bool
run_pipeline_action_available: bool
request_changes_action_available: bool
close_proposal_action_available: bool
review_action_available: bool
approval_action_available: bool
publication_action_available: bool
deactivation_action_available: bool
rollback_action_available: bool
rollback_target_version_id: str | None = None
rollback_target_version_number: int | None = None
requires_decision_notes: bool
requires_code_review_confirmation: bool
class AdminToolGenerationContextResponse(BaseModel):
latest_generation_iteration: int = Field(ge=0)
next_generation_iteration: int = Field(ge=1)
latest_generation_mode: str | None = None
next_generation_mode: str
generation_iterations_count: int = Field(ge=0)
has_previous_generation: bool
pending_change_request: bool
latest_generated_source_checksum: str | None = None
latest_change_request_notes: str | None = None
class AdminToolReviewHistoryEntryResponse(BaseModel):
action_key: str
label: str
summary: str
previous_status: str | None = None
current_status: str | None = None
actor_name: str | None = None
actor_role: str | None = None
decision_notes: str | None = None
reviewed_generated_code: bool | None = None
recorded_at: datetime | None = None
class AdminToolReviewDetailResponse(BaseModel):
service: str
version_id: str
tool_name: str
display_name: str
domain: str
version_number: int = Field(ge=1)
status: ToolLifecycleStatus
summary: str
description: str
business_goal: str
owner_name: str | None = None
parameters: list["AdminToolPublicationParameterResponse"] = Field(default_factory=list)
queue_entry: AdminToolReviewQueueEntryResponse
automated_validations: list["AdminToolAutomatedValidationResponse"] = Field(default_factory=list)
automated_validation_summary: str | None = None
generated_module: str
generated_callable: str
generated_source_code: str
execution: AdminToolPipelineExecutionResponse | None = None
generation_context: AdminToolGenerationContextResponse
human_gate: AdminToolReviewHumanGateResponse
decision_history: list[AdminToolReviewHistoryEntryResponse] = Field(default_factory=list)
next_steps: list[str] = Field(default_factory=list)
class AdminToolPublicationParameterResponse(BaseModel):
name: str
parameter_type: ToolParameterType
description: str
required: bool
class AdminToolPublicationSummaryResponse(BaseModel):
publication_id: str
tool_name: str
display_name: str
description: str
domain: str
version: int
status: ToolLifecycleStatus
version_id: str | None = None
parameter_count: int
parameters: list[AdminToolPublicationParameterResponse] = Field(default_factory=list)
author_name: str | None = None
implementation_module: str
implementation_callable: str
published_by: str | None = None
published_at: datetime | None = None
deactivation_action_available: bool = False
rollback_action_available: bool = False
rollback_target_version_id: str | None = None
rollback_target_version_number: int | None = None
class AdminToolPublicationListResponse(BaseModel):
service: str
source: str
target_service: ServiceName
publications: list[AdminToolPublicationSummaryResponse]
class AdminToolGovernanceTransitionResponse(BaseModel):
service: str
message: str
version_id: str
tool_name: str
version_number: int = Field(ge=1)
status: ToolLifecycleStatus
queue_entry: AdminToolReviewQueueEntryResponse | None = None
publication: AdminToolPublicationSummaryResponse | None = None
next_steps: list[str]
class AdminToolAutomatedValidationResponse(BaseModel):
key: str
label: str
status: str
summary: str
blocking_issues: list[str] = Field(default_factory=list)
class AdminToolPipelineStepResponse(BaseModel):
key: str
label: str
state: str
description: str
class AdminToolPipelineExecutionResponse(BaseModel):
mode: str
target: str
dispatch_state: str | None = None
worker_max_workers: int | None = None
worker_pending_jobs: int | None = None
queued_jobs_before_submit: int | None = None
submitted_at: str | None = None
started_at: str | None = None
completed_at: str | None = None
elapsed_ms: float | None = None
worker_thread_name: str | None = None
poll_after_ms: int | None = None
last_error: str | None = None
class AdminToolGenerationPipelineResponse(BaseModel):
service: str
message: str
version_id: str
tool_name: str
version_number: int = Field(ge=1)
status: ToolLifecycleStatus
current_step: str
steps: list[AdminToolPipelineStepResponse]
queue_entry: AdminToolReviewQueueEntryResponse
automated_validations: list[AdminToolAutomatedValidationResponse] = Field(default_factory=list)
execution: AdminToolPipelineExecutionResponse | None = None
next_steps: list[str]
class AdminToolDraftIntakeParameterRequest(BaseModel):
name: str = Field(min_length=1, max_length=64)
parameter_type: ToolParameterType
description: str = Field(min_length=1, max_length=180)
required: bool = True
@field_validator("name")
@classmethod
def normalize_name(cls, value: str) -> str:
return value.strip().lower()
@field_validator("description")
@classmethod
def normalize_description(cls, value: str) -> str:
return value.strip()
class AdminToolDraftIntakeRequest(BaseModel):
tool_name: str = Field(min_length=3, max_length=64)
display_name: str = Field(min_length=4, max_length=120)
domain: str = Field(min_length=3, max_length=40)
description: str = Field(min_length=16, max_length=280)
business_goal: str = Field(min_length=12, max_length=280)
generation_model: str | None = Field(default=None, max_length=120)
parameters: list[AdminToolDraftIntakeParameterRequest] = Field(default_factory=list, max_length=10)
@field_validator("tool_name")
@classmethod
def normalize_tool_name(cls, value: str) -> str:
return value.strip().lower()
@field_validator("display_name", "description", "business_goal")
@classmethod
def strip_text_fields(cls, value: str) -> str:
return value.strip()
@field_validator("domain")
@classmethod
def normalize_domain(cls, value: str) -> str:
return value.strip().lower()
@field_validator("generation_model", mode="before")
@classmethod
def normalize_generation_model(cls, value: str | None) -> str | None:
if value is None:
return None
normalized = value.strip()
return normalized or None
class AdminToolDraftSubmissionPolicyResponse(BaseModel):
mode: str
submitter_role: StaffRole | None = None
submitter_can_publish_now: bool
submitter_can_authorize_generation_now: bool
direct_publication_blocked: bool
requires_generation_authorization: bool
requires_director_approval: bool
required_approver_role: StaffRole
required_generation_permission: AdminPermission
required_review_permission: AdminPermission
required_publish_permission: AdminPermission
class AdminToolDraftIntakePreviewParameterResponse(BaseModel):
name: str
parameter_type: ToolParameterType
description: str
required: bool
class AdminToolDraftIntakePreviewResponse(BaseModel):
draft_id: str
version_id: str
tool_name: str
display_name: str
domain: str
status: ToolLifecycleStatus
summary: str
business_goal: str
version_number: int = Field(ge=1)
version_count: int = Field(ge=1)
parameter_count: int
required_parameter_count: int
generation_model: str | None = None
requires_director_approval: bool
owner_name: str | None = None
parameters: list[AdminToolDraftIntakePreviewParameterResponse]
class AdminToolDraftIntakeResponse(BaseModel):
service: str
storage_status: str
message: str
submission_policy: AdminToolDraftSubmissionPolicyResponse
draft_preview: AdminToolDraftIntakePreviewResponse
warnings: list[str]
next_steps: list[str]
class AdminCollaboratorSummaryResponse(BaseModel):
id: int
email: str
display_name: str
role: StaffRole
is_active: bool
last_login_at: datetime | None = None
created_at: datetime | None = None
updated_at: datetime | None = None
class AdminCollaboratorListResponse(BaseModel):
service: str
total: int
active_count: int
inactive_count: int
collaborators: list[AdminCollaboratorSummaryResponse]
class AdminCollaboratorCreateRequest(BaseModel):
email: str
display_name: str = Field(min_length=3, max_length=150)
password: str = Field(min_length=1)
is_active: bool = True
@field_validator("email")
@classmethod
def normalize_email(cls, value: str) -> str:
normalized = value.strip().lower()
if "@" not in normalized or normalized.startswith("@") or normalized.endswith("@"):
raise ValueError("email must be a valid administrative login")
return normalized
@field_validator("display_name")
@classmethod
def normalize_display_name(cls, value: str) -> str:
return value.strip()
class AdminCollaboratorCreateResponse(BaseModel):
service: str
message: str
collaborator: AdminCollaboratorSummaryResponse
class AdminCollaboratorStatusUpdateRequest(BaseModel):
is_active: bool
class AdminCollaboratorStatusUpdateResponse(BaseModel):
service: str
message: str
collaborator: AdminCollaboratorSummaryResponse