use std::collections::HashMap;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::infra::cluster::config::ClusterSettings;
use crate::infra::cluster::registry::WorkerHeartbeat;
use crate::infra::execution::placement::{ProjectRuntimePlacement, ProjectRuntimeProfile};
use crate::infra::execution::runner::RunnerCapabilities;
use crate::infra::execution::sync::{ProjectBootstrapPlan, ProjectRuntimeBundle};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum DataAdapterKind {
#[default]
Sqlite,
DynamoDb,
Firebase,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum FileAdapterKind {
#[default]
Filesystem,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlatformConfig {
pub data_root: PathBuf,
pub data_adapter: DataAdapterKind,
pub file_adapter: FileAdapterKind,
pub default_owner: String,
pub default_password: String,
pub default_project: String,
#[serde(default)]
pub secret_rotation_epoch: i64,
pub cluster: ClusterSettings,
}
impl Default for PlatformConfig {
fn default() -> Self {
Self {
data_root: PathBuf::from(".zebflow-platform-data"),
data_adapter: DataAdapterKind::Sqlite,
file_adapter: FileAdapterKind::Filesystem,
default_owner: "superadmin".to_string(),
default_password: String::new(),
default_project: "default".to_string(),
secret_rotation_epoch: 0,
cluster: ClusterSettings::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PlatformUser {
#[serde(default)]
pub user_id: String,
pub owner: String,
pub role: String,
#[serde(default)]
pub git_name: String,
#[serde(default)]
pub git_email: String,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct StoredUser {
pub profile: PlatformUser,
pub auth: PlatformUserLocalAuth,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PlatformUserLocalAuth {
pub user_id: String,
pub password_hash: String,
pub password_alg: String,
pub password_updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PlatformProject {
#[serde(default)]
pub project_id: String,
#[serde(default)]
pub owner_user_id: String,
pub owner: String,
pub project: String,
pub title: String,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProjectCredential {
pub owner: String,
pub project: String,
pub credential_id: String,
pub title: String,
pub kind: String,
pub secret: serde_json::Value,
pub notes: String,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ProjectCredentialListItem {
pub credential_id: String,
pub title: String,
pub kind: String,
pub has_secret: bool,
pub notes: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub auth_roles: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub secure_request_vars: Vec<SecureRequestVariableDefinition>,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub oauth2_status: String,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SecureRequestVariableDefinition {
pub name: String,
pub label: String,
#[serde(default)]
pub value_type: String,
#[serde(default)]
pub required: bool,
#[serde(default)]
pub default_expr: String,
#[serde(default)]
pub description: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProjectDbConnection {
pub owner: String,
pub project: String,
pub connection_id: String,
pub connection_slug: String,
pub connection_label: String,
pub database_kind: String,
pub credential_id: Option<String>,
pub config: serde_json::Value,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ProjectDbConnectionListItem {
pub connection_id: String,
pub connection_slug: String,
pub connection_label: String,
pub database_kind: String,
pub credential_id: Option<String>,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ProjectMarketplaceRepository {
pub owner: String,
pub project: String,
pub repository_id: String,
pub title: String,
pub base_url: String,
pub remote_owner: String,
pub remote_project: String,
pub read_token: String,
pub enabled: bool,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PlatformMarketplaceRepository {
#[serde(default)]
pub source_id: String,
#[serde(default)]
pub owner_user_id: String,
pub owner: String,
pub repository_id: String,
pub title: String,
pub base_url: String,
pub remote_owner: String,
pub remote_project: String,
pub read_token: String,
#[serde(default)]
pub visibility: String,
pub enabled: bool,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MarketplaceAuthority {
pub authority_id: String,
pub host_project_id: String,
pub owner: String,
pub project: String,
pub enabled: bool,
pub public_base_url: String,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PlatformOffice {
pub office_id: String,
pub office_slug: String,
pub label: String,
pub office_kind: String,
pub base_url: String,
pub status: String,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PlatformServiceInstance {
pub service_instance_id: String,
pub service_kind: String,
pub display_label: String,
pub host_office_id: String,
pub state_office_id: String,
pub public_base_url: String,
pub enabled: bool,
pub status: String,
pub placement_generation: i64,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PlatformOfficeNode {
pub office_id: String,
pub node_id: String,
pub label: String,
pub base_url: String,
pub status: String,
#[serde(default)]
pub capabilities: RunnerCapabilities,
pub registered_at: i64,
pub last_heartbeat_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MarketplacePublisher {
#[serde(default, skip_serializing_if = "String::is_empty")]
pub authority_id: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub publisher_pk: String,
pub owner: String,
pub project: String,
pub publisher_id: String,
pub display_name: String,
pub publisher_url: String,
pub email: String,
pub description: String,
pub icon_url: String,
pub website_url: String,
pub enabled: bool,
#[serde(default = "default_true")]
pub can_read: bool,
#[serde(default = "default_true")]
pub can_publish: bool,
#[serde(default)]
pub can_manage: bool,
#[serde(default)]
pub max_packages: i64,
#[serde(default)]
pub max_package_bytes: i64,
#[serde(default)]
pub max_media_files: i64,
#[serde(default)]
pub max_image_bytes: i64,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MarketplaceAssetPackage {
#[serde(default, skip_serializing_if = "String::is_empty")]
pub package_pk: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub authority_id: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub publisher_pk: String,
pub package_id: String,
pub authority_owner: String,
pub authority_project: String,
pub publisher_owner: String,
pub publisher_id: String,
pub publisher_display_name: String,
pub publisher_url: String,
pub publisher_email: String,
pub asset_kind: String,
pub title: String,
pub description: String,
pub visibility: String,
pub tags: Vec<String>,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MarketplaceAssetVersion {
#[serde(default, skip_serializing_if = "String::is_empty")]
pub package_pk: String,
pub package_id: String,
pub version: String,
pub authority_owner: String,
pub authority_project: String,
pub publisher_owner: String,
pub publisher_id: String,
pub source_owner: String,
pub source_project: String,
pub source_kind: String,
pub source_ref: String,
pub artifact_rel_path: String,
pub artifact_sha256: String,
pub manifest: serde_json::Value,
pub created_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MarketplaceToken {
pub token_id: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub authority_id: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub publisher_pk: String,
pub owner: String,
pub project: String,
pub publisher_id: String,
pub publisher_display_name: String,
pub publisher_url: String,
pub publisher_email: String,
pub title: String,
pub secret_hash: String,
pub scopes: Vec<String>,
#[serde(default, skip_serializing)]
pub scope_read: bool,
#[serde(default, skip_serializing)]
pub scope_publish: bool,
#[serde(default, skip_serializing)]
pub scope_manage: bool,
pub expires_at: Option<i64>,
pub last_used_at: Option<i64>,
pub revoked_at: Option<i64>,
pub created_at: i64,
pub updated_at: i64,
}
impl MarketplaceToken {
pub fn grants_scope(&self, required_scope: &str) -> bool {
match required_scope.trim() {
"marketplace:read" => self.scope_read,
"marketplace:publish" => self.scope_publish,
"marketplace:manage" => self.scope_manage,
_ => false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CreateMarketplaceTokenRequest {
pub publisher_id: String,
pub title: String,
#[serde(default)]
pub scopes: Vec<String>,
pub expires_at: Option<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ProjectAssistantConfig {
pub owner: String,
pub project: String,
pub llm_high_credential_id: Option<String>,
pub llm_general_credential_id: Option<String>,
pub max_steps: u32,
pub max_replans: u32,
pub enabled: bool,
pub chat_history_pairs: u32,
pub updated_at: i64,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(rename_all = "snake_case")]
pub enum ProjectCapability {
ProjectRead,
MembersRead,
MembersWrite,
CredentialsRead,
CredentialsWrite,
TemplatesRead,
TemplatesWrite,
TemplatesCreate,
TemplatesDelete,
TemplatesMove,
TemplatesDiagnostics,
PipelinesRead,
PipelinesWrite,
PipelinesCreate,
PipelinesDelete,
PipelinesMove,
PipelinesExecute,
FilesRead,
FilesWrite,
FilesDelete,
TablesRead,
TablesWrite,
LibrariesRead,
LibrariesInstall,
LibrariesRemove,
SettingsRead,
SettingsWrite,
McpSessionCreate,
McpSessionRevoke,
}
impl ProjectCapability {
pub fn key(self) -> &'static str {
match self {
Self::ProjectRead => "project.read",
Self::MembersRead => "members.read",
Self::MembersWrite => "members.write",
Self::CredentialsRead => "credentials.read",
Self::CredentialsWrite => "credentials.write",
Self::TemplatesRead => "templates.read",
Self::TemplatesWrite => "templates.write",
Self::TemplatesCreate => "templates.create",
Self::TemplatesDelete => "templates.delete",
Self::TemplatesMove => "templates.move",
Self::TemplatesDiagnostics => "templates.diagnostics",
Self::PipelinesRead => "pipelines.read",
Self::PipelinesWrite => "pipelines.write",
Self::PipelinesCreate => "pipelines.create",
Self::PipelinesDelete => "pipelines.delete",
Self::PipelinesMove => "pipelines.move",
Self::PipelinesExecute => "pipelines.execute",
Self::FilesRead => "files.read",
Self::FilesWrite => "files.write",
Self::FilesDelete => "files.delete",
Self::TablesRead => "tables.read",
Self::TablesWrite => "tables.write",
Self::LibrariesRead => "libraries.read",
Self::LibrariesInstall => "libraries.install",
Self::LibrariesRemove => "libraries.remove",
Self::SettingsRead => "settings.read",
Self::SettingsWrite => "settings.write",
Self::McpSessionCreate => "mcp.session.create",
Self::McpSessionRevoke => "mcp.session.revoke",
}
}
pub fn from_key(key: &str) -> Option<Self> {
match key {
"project.read" => Some(Self::ProjectRead),
"members.read" => Some(Self::MembersRead),
"members.write" => Some(Self::MembersWrite),
"credentials.read" => Some(Self::CredentialsRead),
"credentials.write" => Some(Self::CredentialsWrite),
"templates.read" => Some(Self::TemplatesRead),
"templates.write" => Some(Self::TemplatesWrite),
"templates.create" => Some(Self::TemplatesCreate),
"templates.delete" => Some(Self::TemplatesDelete),
"templates.move" => Some(Self::TemplatesMove),
"templates.diagnostics" => Some(Self::TemplatesDiagnostics),
"pipelines.read" => Some(Self::PipelinesRead),
"pipelines.write" => Some(Self::PipelinesWrite),
"pipelines.create" => Some(Self::PipelinesCreate),
"pipelines.delete" => Some(Self::PipelinesDelete),
"pipelines.move" => Some(Self::PipelinesMove),
"pipelines.execute" => Some(Self::PipelinesExecute),
"files.read" => Some(Self::FilesRead),
"files.write" => Some(Self::FilesWrite),
"files.delete" => Some(Self::FilesDelete),
"tables.read" => Some(Self::TablesRead),
"tables.write" => Some(Self::TablesWrite),
"libraries.read" => Some(Self::LibrariesRead),
"libraries.install" => Some(Self::LibrariesInstall),
"libraries.remove" => Some(Self::LibrariesRemove),
"settings.read" => Some(Self::SettingsRead),
"settings.write" => Some(Self::SettingsWrite),
"mcp.session.create" => Some(Self::McpSessionCreate),
"mcp.session.revoke" => Some(Self::McpSessionRevoke),
_ => None,
}
}
pub fn all() -> Vec<Self> {
vec![
Self::ProjectRead,
Self::MembersRead,
Self::MembersWrite,
Self::CredentialsRead,
Self::CredentialsWrite,
Self::TemplatesRead,
Self::TemplatesWrite,
Self::TemplatesCreate,
Self::TemplatesDelete,
Self::TemplatesMove,
Self::TemplatesDiagnostics,
Self::PipelinesRead,
Self::PipelinesWrite,
Self::PipelinesCreate,
Self::PipelinesDelete,
Self::PipelinesMove,
Self::PipelinesExecute,
Self::FilesRead,
Self::FilesWrite,
Self::FilesDelete,
Self::TablesRead,
Self::TablesWrite,
Self::LibrariesRead,
Self::LibrariesInstall,
Self::LibrariesRemove,
Self::SettingsRead,
Self::SettingsWrite,
Self::McpSessionCreate,
Self::McpSessionRevoke,
]
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum ProjectAccessRolePreset {
Guest,
#[default]
Reporter,
Developer,
Maintainer,
Owner,
}
impl ProjectAccessRolePreset {
pub const fn key(self) -> &'static str {
match self {
Self::Guest => "guest",
Self::Reporter => "reporter",
Self::Developer => "developer",
Self::Maintainer => "maintainer",
Self::Owner => "owner",
}
}
pub const fn title(self) -> &'static str {
match self {
Self::Guest => "Guest",
Self::Reporter => "Reporter",
Self::Developer => "Developer",
Self::Maintainer => "Maintainer",
Self::Owner => "Owner",
}
}
pub const fn policy_id(self) -> &'static str {
self.key()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ProjectMember {
#[serde(default)]
pub project_id: String,
pub owner: String,
pub project: String,
pub user_id: String,
#[serde(default)]
pub member_user_id: String,
pub role_preset: ProjectAccessRolePreset,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub custom_policy_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub mcp_capability_ceiling: Vec<ProjectCapability>,
#[serde(default)]
pub created_by: String,
#[serde(default)]
pub created_by_user_id: String,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum ProjectInviteStatus {
#[default]
Pending,
Accepted,
Revoked,
Expired,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ProjectInvite {
pub owner: String,
pub project: String,
pub invite_id: String,
pub target_user: String,
pub role_preset: ProjectAccessRolePreset,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub custom_policy_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub mcp_capability_ceiling: Vec<ProjectCapability>,
#[serde(default)]
pub note: String,
#[serde(default)]
pub invited_by: String,
#[serde(default)]
pub status: ProjectInviteStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at: Option<i64>,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UpsertProjectMemberRequest {
pub user_id: String,
pub role_preset: ProjectAccessRolePreset,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub custom_policy_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub mcp_capability_ceiling: Vec<ProjectCapability>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CreateProjectInviteRequest {
pub target_user: String,
pub role_preset: ProjectAccessRolePreset,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub custom_policy_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub mcp_capability_ceiling: Vec<ProjectCapability>,
#[serde(default)]
pub note: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at: Option<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ProjectPolicy {
#[serde(default)]
pub project_id: String,
pub owner: String,
pub project: String,
pub policy_id: String,
pub title: String,
pub capabilities: Vec<ProjectCapability>,
pub managed: bool,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(rename_all = "snake_case")]
pub enum ProjectSubjectKind {
User,
McpSession,
AssistantProfile,
}
impl ProjectSubjectKind {
pub fn key(self) -> &'static str {
match self {
Self::User => "user",
Self::McpSession => "mcp_session",
Self::AssistantProfile => "assistant_profile",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ProjectPolicyBinding {
#[serde(default)]
pub project_id: String,
pub owner: String,
pub project: String,
pub subject_kind: ProjectSubjectKind,
pub subject_id: String,
pub policy_id: String,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ProjectAccessSubject {
pub kind: ProjectSubjectKind,
pub id: String,
}
impl ProjectAccessSubject {
pub fn user(owner: &str) -> Self {
Self {
kind: ProjectSubjectKind::User,
id: slug_segment(owner),
}
}
pub fn mcp_session(token: &str) -> Self {
Self {
kind: ProjectSubjectKind::McpSession,
id: token.to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PipelineMeta {
pub owner: String,
pub project: String,
pub name: String,
pub title: String,
pub virtual_path: String,
pub file_rel_path: String,
pub description: String,
pub trigger_kind: String,
pub hash: String,
pub active_hash: Option<String>,
pub activated_at: Option<i64>,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ProjectDocItem {
pub path: String,
pub name: String,
pub kind: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct AgentDocItem {
pub name: String,
pub user_editable: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PipelineBreadcrumb {
pub name: String,
pub path: String,
pub show_divider: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PipelineFolderItem {
pub name: String,
pub path: String,
pub is_special: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RegistryFileItem {
pub name: String,
pub rel_path: String,
pub kind: String,
pub edit_href: String,
pub git_status: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PipelineRegistryItem {
pub name: String,
pub title: String,
pub description: String,
pub trigger_kind: String,
pub file_rel_path: String,
pub is_active: bool,
pub has_draft: bool,
pub git_status: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PipelineRegistryListing {
pub current_path: String,
pub breadcrumbs: Vec<PipelineBreadcrumb>,
pub folders: Vec<PipelineFolderItem>,
pub pipelines: Vec<PipelineRegistryItem>,
pub files: Vec<RegistryFileItem>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UpsertPipelineDefinitionRequest {
pub file_rel_path: String,
#[serde(default)]
pub title: String,
#[serde(default)]
pub description: String,
#[serde(default)]
pub trigger_kind: String,
pub source: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DeletePipelineRequest {
pub file_rel_path: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GitCommitRequest {
pub files: Vec<String>,
pub message: String,
#[serde(default)]
pub push: bool,
pub credential_id: Option<String>,
pub repo_url: Option<String>,
pub branch: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateSettingsSectionRequest {
pub commit_message: String,
pub data: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PipelineLocateRequest {
pub file_rel_path: String,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum PipelineExecuteTrigger {
Webhook,
Schedule,
Manual,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ExecutePipelineRequest {
pub file_rel_path: String,
pub trigger: PipelineExecuteTrigger,
#[serde(default)]
pub webhook_path: Option<String>,
#[serde(default)]
pub webhook_method: Option<String>,
#[serde(default)]
pub schedule_cron: Option<String>,
#[serde(default)]
pub input: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UpsertProjectDocRequest {
pub path: String,
#[serde(default)]
pub content: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CreateProjectDocFolderRequest {
pub path: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ProjectDocMoveRequest {
pub from_path: String,
pub to_parent_path: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TemplateTreeItem {
pub name: String,
pub rel_path: String,
pub kind: String,
pub depth: usize,
pub file_kind: String,
pub is_protected: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TemplateWorkspaceListing {
pub default_file: Option<String>,
pub items: Vec<TemplateTreeItem>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TemplateGitStatusItem {
pub rel_path: String,
pub code: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TemplateSaveRequest {
pub rel_path: String,
pub content: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum TemplateCreateKind {
Page,
Component,
Script,
Folder,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TemplateCreateRequest {
pub kind: TemplateCreateKind,
pub name: String,
pub parent_rel_path: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TemplateMoveRequest {
pub from_rel_path: String,
pub to_parent_rel_path: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TemplateFilePayload {
pub rel_path: String,
pub name: String,
pub file_kind: String,
pub content: String,
pub line_count: usize,
pub is_protected: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TemplateCompileRequest {
pub rel_path: String,
pub content: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TemplateDiagnostic {
pub code: String,
pub message: String,
pub severity: String,
pub from: Option<usize>,
pub to: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TemplateCompileResponse {
pub ok: bool,
pub diagnostics: Vec<TemplateDiagnostic>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CollectionAttribute {
pub name: String,
pub kind: String,
#[serde(default)]
pub index_types: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SimpleTableDefinition {
pub table: String,
pub title: String,
pub collection: String,
#[serde(default)]
pub attributes: Vec<CollectionAttribute>,
#[serde(default)]
pub hash_indexed_fields: Vec<String>,
#[serde(default)]
pub range_indexed_fields: Vec<String>,
#[serde(default)]
pub fulltext_fields: Vec<String>,
#[serde(default)]
pub vector_fields: Vec<String>,
#[serde(default)]
pub spatial_fields: Vec<String>,
pub row_count: usize,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct UpsertProjectCredentialRequest {
pub credential_id: String,
pub title: String,
pub kind: String,
#[serde(default)]
pub secret: serde_json::Value,
#[serde(default)]
pub notes: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct UpsertProjectDbConnectionRequest {
pub connection_slug: String,
pub connection_label: String,
pub database_kind: String,
pub credential_id: Option<String>,
#[serde(default)]
pub config: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UpsertProjectAssistantConfigRequest {
pub llm_high_credential_id: Option<String>,
pub llm_general_credential_id: Option<String>,
pub max_steps: Option<u32>,
pub max_replans: Option<u32>,
pub enabled: Option<bool>,
pub chat_history_pairs: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TestProjectDbConnectionRequest {
pub connection_slug: Option<String>,
pub database_kind: Option<String>,
pub credential_id: Option<String>,
#[serde(default)]
pub config: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct DescribeProjectDbConnectionRequest {
pub scope: Option<String>,
pub schema: Option<String>,
#[serde(default)]
pub table: Option<String>,
pub include_system: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DbObjectNode {
pub kind: String,
pub name: String,
pub schema: Option<String>,
#[serde(default)]
pub children: Vec<DbObjectNode>,
#[serde(default)]
pub meta: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProjectDbConnectionDescribeResult {
pub connection_id: String,
pub connection_slug: String,
pub database_kind: String,
pub scope: String,
#[serde(default)]
pub nodes: Vec<DbObjectNode>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct QueryProjectDbConnectionRequest {
#[serde(default)]
pub sql: String,
#[serde(default)]
pub params: Vec<serde_json::Value>,
pub table: Option<String>,
pub limit: Option<usize>,
pub read_only: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DbQueryColumn {
pub name: String,
pub data_type: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProjectDbConnectionQueryResult {
pub connection_id: String,
pub connection_slug: String,
pub database_kind: String,
#[serde(default)]
pub columns: Vec<DbQueryColumn>,
#[serde(default)]
pub rows: Vec<Vec<serde_json::Value>>,
pub row_count: usize,
pub truncated: bool,
pub affected_rows: Option<u64>,
pub duration_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProjectDbConnectionTestResult {
pub ok: bool,
pub message: String,
pub details: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CreateSimpleTableRequest {
pub table: String,
pub title: Option<String>,
#[serde(default)]
pub attributes: Vec<CollectionAttribute>,
#[serde(default)]
pub hash_indexed_fields: Vec<String>,
#[serde(default)]
pub range_indexed_fields: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct UpsertSimpleTableRowRequest {
pub table: String,
pub row_id: String,
#[serde(default)]
pub data: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SimpleTableQueryRequest {
pub table: String,
pub where_field: Option<String>,
pub where_value: Option<serde_json::Value>,
#[serde(default = "default_simple_table_limit")]
pub limit: usize,
}
fn default_simple_table_limit() -> usize {
100
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SimpleTableQueryResult {
pub table: SimpleTableDefinition,
pub rows: Vec<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectFileLayout {
pub root: PathBuf,
pub data_dir: PathBuf,
pub data_runtime_dir: PathBuf,
pub data_runtime_pipelines_dir: PathBuf,
pub files_dir: PathBuf,
pub repo_dir: PathBuf,
pub repo_git_dir: PathBuf,
pub repo_pipelines_dir: PathBuf,
pub repo_docs_dir: PathBuf,
pub zebflow_json_file: PathBuf,
pub agent_docs_dir: PathBuf,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateUserRequest {
pub owner: String,
pub password: String,
#[serde(default = "default_member_role")]
pub role: String,
#[serde(default)]
pub git_name: String,
#[serde(default)]
pub git_email: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct UpdateUserSettingsRequest {
#[serde(default)]
pub git_name: String,
#[serde(default)]
pub git_email: String,
}
fn default_member_role() -> String {
"member".to_string()
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum GitIdentitySource {
UserProfile,
ProjectSettings,
Fallback,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ResolvedGitIdentity {
pub name: String,
pub email: String,
pub source: GitIdentitySource,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZebflowJson {
#[serde(default = "default_zebflow_json_version")]
pub version: String,
#[serde(default)]
pub metadata: ZebflowJsonMetadata,
#[serde(default)]
pub configs: ZebflowJsonConfigs,
#[serde(default)]
pub distribution: ZebflowJsonDistribution,
}
fn default_zebflow_json_version() -> String {
"1.0".to_string()
}
fn default_max_asset_size_mb() -> u32 {
10
}
fn default_webhook_body_max_mb() -> u32 {
100
}
pub fn default_pipeline_node_timeout_secs() -> u64 {
30
}
impl Default for ZebflowJson {
fn default() -> Self {
Self {
version: default_zebflow_json_version(),
metadata: ZebflowJsonMetadata::default(),
configs: ZebflowJsonConfigs::default(),
distribution: ZebflowJsonDistribution::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ZebflowJsonMetadata {
#[serde(default)]
pub title: String,
#[serde(default)]
pub description: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ZebflowJsonConfigs {
#[serde(default)]
pub rwe: ZebflowJsonRwe,
#[serde(default)]
pub pipelines: ZebflowJsonPipelines,
#[serde(default)]
pub runtime: ProjectRuntimeProfile,
#[serde(default)]
pub bootstrap: ProjectBootstrapPlan,
#[serde(default)]
pub git: ZebflowJsonGit,
#[serde(default)]
pub assistant: ZebflowJsonAssistant,
#[serde(default)]
pub locks: ZebflowJsonLocks,
#[serde(default)]
pub data: ZebflowJsonData,
#[serde(default)]
pub files: ZebflowJsonFiles,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ZebflowJsonPipelines {
#[serde(default)]
pub logging: ZebflowJsonLogging,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub node_timeout_secs: Option<u64>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub nodes: HashMap<String, Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ZebflowJsonData {}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ZebflowJsonFiles {
#[serde(default)]
pub uploads: ZebflowJsonUploads,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZebflowJsonUploads {
#[serde(default = "default_max_asset_size_mb")]
pub max_asset_size_mb: u32,
#[serde(default = "default_webhook_body_max_mb")]
pub webhook_body_max_mb: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_file_size_mb: Option<u32>,
}
impl Default for ZebflowJsonUploads {
fn default() -> Self {
Self {
max_asset_size_mb: default_max_asset_size_mb(),
webhook_body_max_mb: default_webhook_body_max_mb(),
max_file_size_mb: None,
}
}
}
impl ZebflowJsonUploads {
pub fn effective_max_asset_size_mb(&self) -> u32 {
self.max_asset_size_mb.clamp(5, 100)
}
pub fn effective_webhook_body_max_mb(&self) -> u32 {
self.webhook_body_max_mb
.clamp(default_webhook_body_max_mb(), 512)
}
}
impl ZebflowJsonPipelines {
pub fn effective_node_timeout_secs(&self) -> u64 {
self.node_timeout_secs
.unwrap_or_else(default_pipeline_node_timeout_secs)
.clamp(5, 3600)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ZebflowJsonDistribution {
#[serde(default)]
pub marketplace: ZebflowJsonDistributionMarketplace,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ZebflowJsonDistributionMarketplace {
#[serde(default, skip_serializing_if = "String::is_empty")]
pub entry_url: String,
#[serde(default)]
pub as_app: bool,
#[serde(default)]
pub producer_enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ZebflowJsonLocks {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub templates: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ZebflowJsonGit {
#[serde(default)]
pub remote: ZebflowJsonGitRemote,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ZebflowJsonGitRemote {
#[serde(default)]
pub credential_id: String,
#[serde(default)]
pub repo_url: String,
#[serde(default)]
pub branch: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ZebflowJsonRwe {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allow_list: Vec<String>,
#[serde(default)]
pub minify_html: bool,
#[serde(default = "default_rwe_strict_mode")]
pub strict_mode: bool,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub libraries: ZebflowJsonRweLibraries,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub deployment_asset_base: Option<String>,
}
pub type ZebflowJsonRweLibraries = HashMap<String, ZebflowJsonRweLibraryEntry>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZebflowJsonRweLibraryEntry {
pub version: String,
pub source: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ZebLock {
pub version: u32,
#[serde(default)]
pub libraries: HashMap<String, ZebLockEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZebLockEntry {
pub version: String,
pub source: String,
pub entry: String,
pub integrity: Option<String>,
}
pub fn default_rwe_strict_mode() -> bool {
true
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpsertRweSettingsRequest {
#[serde(default)]
pub allow_list: Vec<String>,
#[serde(default)]
pub minify_html: bool,
#[serde(default = "default_rwe_strict_mode")]
pub strict_mode: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ZebflowJsonLogging {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_invocations: Option<u32>,
}
impl ZebflowJsonLogging {
pub fn effective_max_invocations(&self) -> usize {
self.max_invocations.unwrap_or(20).max(1) as usize
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PipelineInvocationEntry {
#[serde(default, skip_serializing_if = "String::is_empty")]
pub run_id: String,
pub at: i64,
pub duration_ms: u64,
pub status: String,
pub trigger: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub trace: Vec<crate::pipeline::model::NodeTraceEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ZebflowJsonAssistant {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub high_model_credential: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub general_model_credential: Option<String>,
#[serde(default)]
pub max_steps: Option<u32>,
#[serde(default)]
pub max_replans: Option<u32>,
#[serde(default)]
pub chat_history_pairs: Option<u32>,
#[serde(default)]
pub enabled: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateProjectRequest {
pub project: String,
pub title: Option<String>,
#[serde(default)]
pub local_branch: Option<String>,
#[serde(default)]
pub runtime: ProjectRuntimeSelectionRequest,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct ProjectRuntimeSelectionRequest {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub runtime_mode: Option<crate::infra::execution::placement::ProjectRuntimeMode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub placement_worker_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct ProjectRuntimeMaterializationRequest {
#[serde(default)]
pub bundle: ProjectRuntimeBundle,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub credentials: Vec<ProjectCredential>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub db_connections: Vec<ProjectDbConnection>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct ClusterWorkerRegisterRequest {
pub node_id: String,
#[serde(default)]
pub label: String,
#[serde(default)]
pub base_url: String,
#[serde(default)]
pub capabilities: RunnerCapabilities,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct ClusterWorkerHeartbeatRequest {
pub node_id: String,
#[serde(default)]
pub status: String,
#[serde(default)]
pub base_url: String,
#[serde(default)]
pub capabilities: RunnerCapabilities,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ClusterWorkerHeartbeatResponse {
pub ok: bool,
pub heartbeat: WorkerHeartbeat,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct ClusterRuntimeTargetOption {
pub value: String,
pub label: String,
#[serde(default)]
pub description: String,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ProjectTransferArtifactKind {
Bundle,
Files,
}
impl ProjectTransferArtifactKind {
pub fn key(self) -> &'static str {
match self {
Self::Bundle => "bundle",
Self::Files => "files",
}
}
pub fn archive_name(self) -> &'static str {
match self {
Self::Bundle => "project.bundle.tar",
Self::Files => "project.files.tar",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProjectTransferManifest {
pub schema_version: String,
pub owner: String,
pub project: String,
pub artifact_kind: ProjectTransferArtifactKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_office_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_controller_id: Option<String>,
pub exported_at: i64,
#[serde(default)]
pub runtime_profile: ProjectRuntimeProfile,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub placement: Option<ProjectRuntimePlacement>,
#[serde(default)]
pub repo_file_count: u64,
#[serde(default)]
pub data_file_count: u64,
#[serde(default)]
pub files_file_count: u64,
#[serde(default)]
pub total_bytes: u64,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ProjectOperationKind {
ExportBundle,
ExportFiles,
ImportBundle,
ImportFiles,
}
impl ProjectOperationKind {
pub fn key(self) -> &'static str {
match self {
Self::ExportBundle => "export_bundle",
Self::ExportFiles => "export_files",
Self::ImportBundle => "import_bundle",
Self::ImportFiles => "import_files",
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ProjectOperationStatus {
Pending,
Running,
Failed,
Completed,
}
impl ProjectOperationStatus {
pub fn key(self) -> &'static str {
match self {
Self::Pending => "pending",
Self::Running => "running",
Self::Failed => "failed",
Self::Completed => "completed",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ProjectOperationRecord {
pub operation_id: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub project_id: String,
pub owner: String,
pub project: String,
pub kind: ProjectOperationKind,
pub status: ProjectOperationStatus,
#[serde(default)]
pub current_step: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_office_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target_office_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub artifact_rel_path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub artifact_sha256: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub artifact_bytes: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error_message: Option<String>,
#[serde(default)]
pub retry_count: u32,
pub created_at: i64,
pub updated_at: i64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub completed_at: Option<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoginRequest {
pub identifier: String,
pub password: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthSession {
pub owner: String,
}
pub fn now_ts() -> i64 {
use std::time::{SystemTime, UNIX_EPOCH};
match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(d) => d.as_secs() as i64,
Err(_) => 0,
}
}
pub fn slug_segment(raw: &str) -> String {
raw.trim()
.to_ascii_lowercase()
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || c == '-' || c == '_' {
c
} else {
'-'
}
})
.collect::<String>()
.trim_matches('-')
.to_string()
}
pub fn normalize_virtual_path(raw: &str) -> String {
let trimmed = raw.trim();
if trimmed.is_empty() || trimmed == "/" {
return "/".to_string();
}
let mut parts = Vec::new();
for seg in trimmed.split('/') {
let seg = seg.trim();
if seg.is_empty() || seg == "." || seg == ".." {
continue;
}
let slug = slug_segment(seg);
if !slug.is_empty() {
parts.push(slug);
}
}
if parts.is_empty() {
"/".to_string()
} else {
format!("/{}", parts.join("/"))
}
}
fn default_true() -> bool {
true
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpSession {
pub owner: String,
pub project: String,
pub capabilities: Vec<ProjectCapability>,
pub token: String,
#[serde(default)]
pub created_at: i64,
#[serde(default)]
pub auto_reset_seconds: Option<u64>,
#[serde(default = "default_true")]
pub enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpSessionCreateRequest {
pub capabilities: Vec<String>,
#[serde(default)]
pub auto_reset_seconds: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpSessionToggleRequest {
pub enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpSessionResponse {
pub token: String,
pub mcp_url: String,
pub capabilities: Vec<String>,
pub enabled: bool,
pub created_at: i64,
#[serde(default)]
pub auto_reset_seconds: Option<u64>,
pub rotation_epoch: i64,
}
pub fn mcp_tool_capability(tool_name: &str) -> Option<ProjectCapability> {
match tool_name {
"list_pipelines" => Some(ProjectCapability::PipelinesRead),
"get_pipeline" => Some(ProjectCapability::PipelinesRead),
"upsert_pipeline" => Some(ProjectCapability::PipelinesWrite),
"activate_pipeline" => Some(ProjectCapability::PipelinesWrite),
"deactivate_pipeline" => Some(ProjectCapability::PipelinesWrite),
"execute_pipeline" => Some(ProjectCapability::PipelinesExecute),
"list_templates" => Some(ProjectCapability::TemplatesRead),
"get_template" => Some(ProjectCapability::TemplatesRead),
"save_template" => Some(ProjectCapability::TemplatesWrite),
"create_template" => Some(ProjectCapability::TemplatesCreate),
"delete_template" => Some(ProjectCapability::TemplatesDelete),
"get_credential" => Some(ProjectCapability::CredentialsRead),
"upsert_credential" => Some(ProjectCapability::CredentialsWrite),
"list_db_connections" => Some(ProjectCapability::TablesRead),
"get_db_connection" => Some(ProjectCapability::TablesRead),
"upsert_db_connection" => Some(ProjectCapability::TablesWrite),
"update_db_connection" => Some(ProjectCapability::TablesWrite),
"delete_db_connection" => Some(ProjectCapability::TablesWrite),
"test_db_connection" => Some(ProjectCapability::TablesRead),
"describe_db_connection" => Some(ProjectCapability::TablesRead),
"query_db_connection" => Some(ProjectCapability::TablesRead),
"list_db_connection_schemas" => Some(ProjectCapability::TablesRead),
"list_db_connection_tables" => Some(ProjectCapability::TablesRead),
"list_db_connection_functions" => Some(ProjectCapability::TablesRead),
"preview_db_connection_table" => Some(ProjectCapability::TablesRead),
"list_connections" => Some(ProjectCapability::TablesRead),
"describe_connection" => Some(ProjectCapability::TablesRead),
"list_project_docs" => Some(ProjectCapability::ProjectRead),
"read_project_doc" => Some(ProjectCapability::ProjectRead),
"create_project_doc" => Some(ProjectCapability::FilesWrite),
"list_credentials" => Some(ProjectCapability::CredentialsRead),
"execute_pipeline_dsl" => Some(ProjectCapability::PipelinesExecute),
"describe_pipeline" => Some(ProjectCapability::PipelinesRead),
"register_pipeline" => Some(ProjectCapability::PipelinesWrite),
"patch_pipeline" => Some(ProjectCapability::PipelinesWrite),
"run_ephemeral" => Some(ProjectCapability::PipelinesExecute),
"git_command" => Some(ProjectCapability::PipelinesWrite),
"write_template" => Some(ProjectCapability::TemplatesWrite),
"write_doc" => Some(ProjectCapability::TemplatesWrite),
"list_agent_docs" => Some(ProjectCapability::SettingsRead),
"read_agent_doc" => Some(ProjectCapability::SettingsRead),
"write_agent_doc" => Some(ProjectCapability::SettingsWrite),
"pipeline_list" => Some(ProjectCapability::PipelinesRead),
"pipeline_get" => Some(ProjectCapability::PipelinesRead),
"pipeline_register" => Some(ProjectCapability::PipelinesWrite),
"pipeline_describe" => Some(ProjectCapability::PipelinesRead),
"pipeline_patch" => Some(ProjectCapability::PipelinesWrite),
"pipeline_activate" => Some(ProjectCapability::PipelinesWrite),
"pipeline_deactivate" => Some(ProjectCapability::PipelinesWrite),
"pipeline_execute" => Some(ProjectCapability::PipelinesExecute),
"pipeline_run" => Some(ProjectCapability::PipelinesExecute),
"pipeline_get_invocations" => Some(ProjectCapability::PipelinesRead),
"template_list" => Some(ProjectCapability::TemplatesRead),
"template_get" => Some(ProjectCapability::TemplatesRead),
"template_create" => Some(ProjectCapability::TemplatesCreate),
"template_write" => Some(ProjectCapability::TemplatesWrite),
"template_search" => Some(ProjectCapability::TemplatesRead),
"template_edit" => Some(ProjectCapability::TemplatesWrite),
"template_outline" => Some(ProjectCapability::TemplatesRead),
"template_deps" => Some(ProjectCapability::TemplatesRead),
"template_batch_edit" => Some(ProjectCapability::TemplatesWrite),
"pipeline_search" => Some(ProjectCapability::PipelinesRead),
"connection_list" => Some(ProjectCapability::TablesRead),
"connection_describe" => Some(ProjectCapability::TablesRead),
"credential_list" => Some(ProjectCapability::CredentialsRead),
"docs_project_list" => Some(ProjectCapability::ProjectRead),
"docs_project_read" => Some(ProjectCapability::ProjectRead),
"docs_project_write" => Some(ProjectCapability::TemplatesWrite),
"docs_agent_list" => Some(ProjectCapability::SettingsRead),
"docs_agent_read" => Some(ProjectCapability::SettingsRead),
"docs_agent_write" => Some(ProjectCapability::SettingsWrite),
"start_here" => Some(ProjectCapability::ProjectRead),
"help" => Some(ProjectCapability::ProjectRead),
"help_search" => Some(ProjectCapability::ProjectRead),
"list_ui_catalog" => Some(ProjectCapability::PipelinesRead),
"install_ui_components" => Some(ProjectCapability::PipelinesWrite),
"move_resource" => Some(ProjectCapability::PipelinesWrite),
_ => None,
}
}