use std::collections::{BTreeMap, HashMap};
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use crate::infra::cluster::config::ClusterSettings;
use crate::infra::cluster::registry::WorkerHeartbeat;
use crate::infra::execution::placement::ProjectRuntimeProfile;
use crate::infra::execution::runner::RunnerCapabilities;
use crate::zebfs::{FileBackend, FileStore, ZebFs, ZebFsError};
#[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: crate::platform::boot::default_data_root(),
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,
}
pub const CREDENTIAL_STATE_GENERATED: &str = "generated";
pub const CREDENTIAL_STATE_CHOSEN: &str = "chosen";
fn default_credential_state() -> String {
CREDENTIAL_STATE_CHOSEN.to_string()
}
#[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,
#[serde(default = "default_credential_state")]
pub credential_state: String,
}
#[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)]
#[serde(deny_unknown_fields)]
pub struct CredentialTypeDef {
pub kind: String,
pub title: String,
#[serde(default)]
pub description: String,
pub fields: Vec<CredentialFieldDef>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub placeholders: HashMap<String, String>,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub config_key: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct CredentialFieldDef {
pub key: String,
pub label: String,
#[serde(default = "default_field_type", rename = "type")]
pub field_type: String,
#[serde(default)]
pub required: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub placeholder: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub help: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub generate: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub options: Vec<CredentialFieldOption>,
#[serde(default, rename = "fullWidth")]
pub full_width: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rows: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CredentialFieldOption {
pub value: String,
pub label: String,
}
fn default_field_type() -> String {
"text".to_string()
}
#[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 ProjectHubRepository {
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 PlatformHubRepository {
#[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 kind: String,
#[serde(default)]
pub priority: i64,
#[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 HubAccessGrant {
pub grant_id: String,
pub source_owner: String,
pub source_id: String,
pub repository_id: String,
pub grant_scope: String,
pub target_owner: String,
pub target_project: String,
pub can_read: bool,
pub can_publish: bool,
pub can_manage: bool,
pub enabled: bool,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct HubAuthority {
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,
}
pub const JOIN_TOKEN_STATUS_ACTIVE: &str = "active";
pub const JOIN_TOKEN_STATUS_REVOKED: &str = "revoked";
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PlatformOfficeJoinToken {
pub office_id: String,
#[serde(skip_serializing, default)]
pub secret_digest: String,
pub status: String,
#[serde(default)]
pub note: String,
pub created_at: i64,
#[serde(default)]
pub last_used_at: i64,
}
impl PlatformOfficeJoinToken {
pub fn is_active(&self) -> bool {
self.status.trim() == JOIN_TOKEN_STATUS_ACTIVE
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PlatformOfficeVouchRedemption {
pub nonce: String,
pub office_id: String,
pub identity: String,
pub expires_at: i64,
pub redeemed_at: i64,
}
pub const IDENTITY_WRITE_ACTION_LINKED: &str = "linked";
pub const IDENTITY_WRITE_ACTION_CREATED: &str = "created";
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PlatformOfficeIdentityWrite {
pub write_id: String,
pub office_id: String,
pub owner: String,
pub action: String,
pub role: String,
pub source: String,
pub detail: String,
pub written_at: i64,
}
pub const LOCAL_AUTHORITY_EVENT_BREAK_GLASS: &str = "break-glass";
pub const LOCAL_AUTHORITY_EVENT_DETACH: &str = "detach";
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PlatformOfficeLocalAuthorityEvent {
pub event_id: String,
pub office_id: String,
pub event: String,
#[serde(default)]
pub join_fingerprint: String,
#[serde(default)]
pub owner: String,
#[serde(default)]
pub detail: String,
pub acted_at: i64,
#[serde(default)]
pub reported_at: i64,
}
impl PlatformOfficeLocalAuthorityEvent {
pub fn is_break_glass(&self) -> bool {
self.event.trim() == LOCAL_AUTHORITY_EVENT_BREAK_GLASS
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ClusterOfficeVouch {
pub office_id: String,
pub identity: String,
pub expires_at: i64,
pub ttl_seconds: i64,
pub vouch: String,
#[serde(default)]
pub redeem_url: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AcceptedOfficeVouch {
pub office_id: String,
pub identity: String,
pub expires_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 HubPublisher {
#[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, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct HubAssetMedia {
pub name: String,
pub role: String,
pub content_type: String,
pub size_bytes: usize,
pub artifact_sha256: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct HubAssetGallery {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cover: Option<HubAssetGalleryImage>,
#[serde(default)]
pub items: Vec<HubAssetGalleryItem>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct HubAssetGalleryImage {
pub kind: String,
pub media_name: String,
#[serde(default)]
pub alt: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct HubAssetGalleryItem {
pub kind: String,
#[serde(default)]
pub media_name: String,
#[serde(default)]
pub alt: String,
#[serde(default)]
pub url: String,
#[serde(default)]
pub title: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct HubAssetPackage {
#[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,
#[serde(default)]
pub summary: String,
#[serde(default)]
pub description_md: String,
#[serde(default)]
pub image_url: String,
#[serde(default)]
pub media: Vec<HubAssetMedia>,
#[serde(default)]
pub gallery: HubAssetGallery,
pub visibility: String,
pub tags: Vec<String>,
#[serde(default)]
pub retracted_at: Option<i64>,
#[serde(default)]
pub retracted_reason: String,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct HubAssetVersion {
#[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,
#[serde(default)]
pub retracted_at: Option<i64>,
#[serde(default)]
pub retracted_reason: String,
pub created_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct HubToken {
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 HubToken {
pub fn grants_scope(&self, required_scope: &str) -> bool {
match required_scope.trim() {
"hub:read" => self.scope_read,
"hub:publish" => self.scope_publish,
"hub:manage" => self.scope_manage,
_ => false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CreateHubTokenRequest {
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,
ProjectDelete,
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::ProjectDelete => "project.delete",
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::ProjectDelete,
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,
}
impl ProjectInviteStatus {
pub fn key(self) -> &'static str {
match self {
Self::Pending => "pending",
Self::Accepted => "accepted",
Self::Revoked => "revoked",
Self::Expired => "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 {
#[serde(default)]
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,
#[serde(default)]
pub trigger: Option<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>,
#[serde(default)]
pub path: String,
pub items: Vec<TemplateTreeItem>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct RepoTreeScope {
pub path: String,
pub depth: Option<usize>,
}
impl RepoTreeScope {
pub fn all() -> Self {
Self {
path: String::new(),
depth: None,
}
}
pub fn children_of(path: impl Into<String>) -> Self {
Self {
path: path.into(),
depth: Some(1),
}
}
}
#[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, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct SimpleTableDefinition {
pub table: 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,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CredentialKeyGeneration {
pub key_id: u32,
pub created_at: i64,
pub current: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CredentialKeyringReport {
pub source: String,
pub key_file: String,
pub current_key_id: u32,
pub generations: Vec<CredentialKeyGeneration>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CredentialSweepReport {
pub key_id: u32,
pub examined: usize,
pub rewritten: usize,
pub migrated_from_plaintext: usize,
pub compacted: bool,
}
#[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, Eq)]
pub struct DbTypeDef {
pub name: String,
pub family: DbTypeFamily,
pub parameterized: bool,
pub note: String,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum DbTypeFamily {
#[default]
Text,
Number,
Boolean,
Json,
DateTime,
Uuid,
Binary,
Geometry,
Vector,
Other,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DbCapabilities {
pub inline_edit: bool,
pub create_table: bool,
pub drop_table: bool,
pub maintenance: bool,
pub schemas: bool,
pub edit_table_properties: bool,
pub row_identity: String,
pub geo: bool,
pub relations: DbRelationStyle,
}
impl Default for DbCapabilities {
fn default() -> Self {
Self {
inline_edit: false,
create_table: false,
drop_table: false,
maintenance: false,
schemas: false,
edit_table_properties: false,
row_identity: String::new(),
geo: false,
relations: DbRelationStyle::None,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum DbRelationStyle {
#[default]
None,
ForeignKey,
Graph,
}
#[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 capabilities: DbCapabilities,
#[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,
#[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 UpdateSimpleTableRequest {
#[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)]
pub struct ProjectFileLayout {
pub root: PathBuf,
pub data_dir: PathBuf,
pub files_dir: PathBuf,
pub repo_dir: PathBuf,
pub repo_git_dir: PathBuf,
pub project_config_file: PathBuf,
pub repo_layout: ResolvedProjectLayout,
pub file_store: FileStore,
}
impl ProjectFileLayout {
pub fn open_files(&self) -> ZebFs {
crate::zebfs::backend::open(&self.file_store)
}
pub fn file_backend(&self) -> FileBackend {
self.file_store.backend()
}
pub fn local_files_dir(&self) -> Result<&Path, ZebFsError> {
match &self.file_store {
FileStore::Local(dir) => Ok(dir),
FileStore::S3(config) => Err(ZebFsError::new(
"ZEBFS_LOCAL_ONLY",
format!(
"this project's files live in the bucket '{}', and this operation streams from local disk; it is not available on an object store yet",
config.bucket
),
)),
}
}
pub fn repo_source_dir(&self) -> PathBuf {
self.repo_dir.join(&self.repo_layout.source)
}
pub fn repo_static_dir(&self) -> PathBuf {
self.repo_dir.join(&self.repo_layout.r#static)
}
pub fn repo_docs_dir(&self) -> PathBuf {
self.repo_dir.join(&self.repo_layout.docs)
}
pub fn repo_node_interfaces_dir(&self) -> PathBuf {
self.repo_dir.join(&self.repo_layout.node_interfaces)
}
pub fn data_store_dir(&self) -> PathBuf {
self.data_dir.join("store")
}
pub fn data_store_sekejap_dir(&self) -> PathBuf {
self.data_store_dir().join("sekejap")
}
pub fn data_store_local_db_file(&self) -> PathBuf {
self.data_store_dir().join("local.db")
}
pub fn data_store_kv_db_file(&self) -> PathBuf {
self.data_store_dir().join("kv.db")
}
pub fn data_store_chat_history_file(&self) -> PathBuf {
self.data_store_dir().join("chat_history.json")
}
pub fn data_store_assistant_dir(&self) -> PathBuf {
self.data_store_dir().join("assistant")
}
pub fn data_store_assistant_memory_file(&self, user_id: &str) -> PathBuf {
self.data_store_assistant_dir()
.join(slug_segment(user_id))
.join("memory.md")
}
pub fn data_cache_dir(&self) -> PathBuf {
self.data_dir.join("cache")
}
pub fn data_cache_pipelines_dir(&self) -> PathBuf {
self.data_cache_dir().join("pipelines")
}
pub fn data_cache_agent_docs_dir(&self) -> PathBuf {
self.data_cache_dir().join("agent_docs")
}
pub fn data_cache_mapserver_artifacts_dir(&self) -> PathBuf {
self.data_cache_dir().join("mapserver-artifacts")
}
pub fn ensure_mapserver_artifacts_home(&self) -> std::io::Result<PathBuf> {
let new_root = self.data_cache_mapserver_artifacts_dir();
crate::infra::io::durable::migrate_tier_entry(
&self.files_dir.join("mapserver").join(".artifacts"),
&new_root,
)?;
Ok(new_root)
}
pub fn resolve_mapserver_artifact_path(&self, rel: &str) -> PathBuf {
let rel = rel.trim_start_matches('/');
if let Some(rest) = rel.strip_prefix("mapserver/.artifacts/") {
self.data_cache_mapserver_artifacts_dir().join(rest)
} else if let Some(rest) = rel.strip_prefix("mapserver-artifacts/") {
self.data_cache_mapserver_artifacts_dir().join(rest)
} else {
self.files_dir.join(rel)
}
}
pub fn data_hub_dir(&self) -> PathBuf {
self.data_dir.join("hub")
}
pub fn data_hub_nodes_dir(&self) -> PathBuf {
self.data_hub_dir().join("nodes")
}
pub fn data_hub_rwe_libraries_dir(&self) -> PathBuf {
self.data_hub_dir().join("rwe-libraries")
}
pub fn data_recovery_dir(&self) -> PathBuf {
self.data_dir.join("recovery")
}
pub fn data_logs_dir(&self) -> PathBuf {
self.data_dir.join("logs")
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum NodePackageSource {
Composite,
Wasm,
Declarative,
}
impl NodePackageSource {
pub const fn as_str(self) -> &'static str {
match self {
Self::Composite => "composite",
Self::Wasm => "wasm",
Self::Declarative => "declarative",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct WasmModuleSpec {
pub path: String,
pub abi: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(deny_unknown_fields)]
pub struct RunBinding {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub function: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub module: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub export: Option<String>,
}
impl RunBinding {
pub fn source(&self) -> Option<NodePackageSource> {
match (&self.function, &self.module, &self.export) {
(Some(_), None, None) => Some(NodePackageSource::Composite),
(None, Some(_), Some(_)) => Some(NodePackageSource::Wasm),
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct NodePackageManifest {
pub source: NodePackageSource,
#[serde(default)]
pub package: String,
pub version: String,
#[serde(default)]
pub hosts: Vec<String>,
pub definition: crate::pipeline::NodeDefinition,
#[serde(default)]
pub credentials: Vec<CredentialTypeDef>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub run: Option<RunBinding>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub trigger: Option<PackageTriggerConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lifecycle: Option<PackageLifecycleConfig>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub functions: BTreeMap<String, String>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub modules: BTreeMap<String, WasmModuleSpec>,
}
impl NodePackageManifest {
pub fn main_function(&self) -> Option<&str> {
self.run.as_ref()?.function.as_deref()
}
pub fn wasm_target(&self) -> Option<(&WasmModuleSpec, &str)> {
let run = self.run.as_ref()?;
let module = run.module.as_deref()?;
let export = run.export.as_deref()?;
Some((self.modules.get(module)?, export))
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct PackageTriggerConfig {
#[serde(rename = "type")]
pub trigger_type: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path_template: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(deny_unknown_fields)]
pub struct PackageLifecycleConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub on_activate: Option<RunBinding>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub on_deactivate: Option<RunBinding>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct MultiNodePackageDefinition {
pub package: String,
pub version: String,
pub title: String,
#[serde(default)]
pub description: String,
#[serde(default)]
pub icon: String,
#[serde(default)]
pub credentials: Vec<CredentialTypeDef>,
#[serde(default)]
pub hosts: Vec<String>,
#[serde(default)]
pub functions: BTreeMap<String, String>,
#[serde(default)]
pub modules: BTreeMap<String, WasmModuleSpec>,
pub nodes: Vec<MultiNodeEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct MultiNodeEntry {
pub kind: String,
pub title: String,
#[serde(default)]
pub description: String,
#[serde(default)]
pub icon: String,
#[serde(default)]
pub ui_category: String,
#[serde(default)]
pub ui_category_label: String,
#[serde(default)]
pub uses_credentials: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub run: Option<RunBinding>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub trigger: Option<PackageTriggerConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lifecycle: Option<PackageLifecycleConfig>,
pub definition: MultiNodeEntryDefinition,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct MultiNodeEntryDefinition {
#[serde(default)]
pub input_pins: Vec<String>,
#[serde(default)]
pub output_pins: Vec<String>,
#[serde(default)]
pub config_schema: serde_json::Value,
#[serde(default)]
pub input_schema: serde_json::Value,
#[serde(default)]
pub output_schema: serde_json::Value,
#[serde(default)]
pub examples: Vec<crate::pipeline::NodeExample>,
#[serde(default)]
pub failure_semantics: Vec<crate::pipeline::NodeFailureSemantic>,
#[serde(default)]
pub fields: Vec<crate::pipeline::NodeFieldDef>,
#[serde(default)]
pub layout: Vec<crate::pipeline::model::LayoutItem>,
#[serde(default)]
pub dsl_flags: Vec<crate::pipeline::model::DslFlag>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InstalledNodePackage {
pub slug: String,
pub owner: String,
pub project: String,
pub manifest: NodePackageManifest,
pub package_dir: String,
pub icon_rel_path: Option<String>,
}
#[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)]
#[serde(deny_unknown_fields)]
pub struct ZebflowJson {
#[serde(default)]
pub metadata: ZebflowJsonMetadata,
#[serde(default)]
pub configs: ZebflowJsonConfigs,
#[serde(default)]
pub distribution: ZebflowJsonDistribution,
}
fn default_max_asset_size_mb() -> u32 {
10
}
fn default_webhook_body_max_mb() -> u32 {
100
}
pub const MAX_UPLOAD_SIZE_MB: u32 = 1024;
pub fn default_pipeline_node_timeout_secs() -> u64 {
30
}
impl Default for ZebflowJson {
fn default() -> Self {
Self {
metadata: ZebflowJsonMetadata::default(),
configs: ZebflowJsonConfigs::default(),
distribution: ZebflowJsonDistribution::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ZebflowJsonMetadata {
#[serde(default)]
pub title: String,
#[serde(default)]
pub description: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct ProjectBootstrapPlan {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub activate: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ZebflowJsonConfigs {
#[serde(default, skip_serializing_if = "ZebflowJsonLayout::is_undeclared")]
pub layout: ZebflowJsonLayout,
#[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,
}
pub const DEFAULT_LAYOUT_SOURCE_DIR: &str = "";
pub const DEFAULT_LAYOUT_STATIC_SUBDIR: &str = "static";
pub const DEFAULT_LAYOUT_DOCS_DIR: &str = "docs";
pub const DEFAULT_LAYOUT_SCHEMA_DIR: &str = "schemas/sekejap";
pub const DEFAULT_LAYOUT_SQLITE_SCHEMA_DIR: &str = "schemas/sqlite";
pub const DEFAULT_LAYOUT_NODE_INTERFACES_DIR: &str = "nodes";
pub const DEFAULT_ALLOWED_FILE_EXTENSIONS: &[&str] = &[
"css", "geojson", "js", "json", "jsx", "md", "mjs", "sql", "ts", "tsx", "txt", "webmanifest", "xml",
"yaml", "yml", "csv", "gif", "ico", "jpeg", "jpg", "mp3", "mp4", "pdf", "png", "svg", "ttf", "webp", "woff",
"woff2",
];
pub const ALWAYS_ALLOWED_FILE_NAMES: &[&str] = &[
".gitkeep",
"schema.json",
"zeb.lock",
"zebflow.init.json",
"zebflow.yaml",
];
pub const PIPELINE_DEFINITION_EXTENSION: &str = ".zf.json";
pub const LEGACY_PIPELINE_IDENTITY_ROOT: &str = "pipelines";
pub const PIPELINE_IDENTITY_BACKUP_FILE: &str = "pipeline-identity";
pub const SCHEMA_DOCUMENT_FILE: &str = "schema.json";
pub const SQLITE_SCHEMA_DOCUMENT_FILE: &str = "schema.sql";
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct ZebflowJsonLayout {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub r#static: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub docs: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub schema: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sqlite_schema: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub node_interfaces: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub initial_data: Option<Vec<ZebflowJsonInitialDataDir>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub allowed_extensions: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct ZebflowJsonInitialDataDir {
pub path: String,
pub engine: String,
}
pub fn default_initial_data_dirs() -> Vec<ZebflowJsonInitialDataDir> {
crate::platform::policy::package::INITIAL_DATA_DIRS
.iter()
.map(|(path, engine)| ZebflowJsonInitialDataDir {
path: (*path).to_string(),
engine: (*engine).to_string(),
})
.collect()
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ResolvedProjectLayout {
pub source: String,
pub r#static: String,
pub docs: String,
pub schema: String,
pub sqlite_schema: String,
pub node_interfaces: String,
pub initial_data: Vec<ZebflowJsonInitialDataDir>,
pub allowed_extensions: Vec<String>,
}
impl ZebflowJsonLayout {
pub fn is_undeclared(&self) -> bool {
*self == Self::default()
}
pub fn resolve(&self) -> ResolvedProjectLayout {
let resolved = |declared: &Option<String>, fallback: &str| {
declared.clone().unwrap_or_else(|| fallback.to_string())
};
let source = resolved(&self.source, DEFAULT_LAYOUT_SOURCE_DIR);
let r#static = self.r#static.clone().unwrap_or_else(|| {
if source.is_empty() {
DEFAULT_LAYOUT_STATIC_SUBDIR.to_string()
} else {
format!("{source}/{DEFAULT_LAYOUT_STATIC_SUBDIR}")
}
});
ResolvedProjectLayout {
source,
r#static,
docs: resolved(&self.docs, DEFAULT_LAYOUT_DOCS_DIR),
schema: resolved(&self.schema, DEFAULT_LAYOUT_SCHEMA_DIR),
sqlite_schema: resolved(&self.sqlite_schema, DEFAULT_LAYOUT_SQLITE_SCHEMA_DIR),
node_interfaces: resolved(&self.node_interfaces, DEFAULT_LAYOUT_NODE_INTERFACES_DIR),
initial_data: self
.initial_data
.clone()
.unwrap_or_else(default_initial_data_dirs),
allowed_extensions: resolve_allowed_extensions(self.allowed_extensions.as_deref()),
}
}
}
fn resolve_allowed_extensions(declared: Option<&[String]>) -> Vec<String> {
let Some(declared) = declared else {
return DEFAULT_ALLOWED_FILE_EXTENSIONS
.iter()
.map(|value| (*value).to_string())
.collect();
};
let mut resolved = declared
.iter()
.map(|value| value.trim().trim_start_matches('.').to_ascii_lowercase())
.filter(|value| DEFAULT_ALLOWED_FILE_EXTENSIONS.contains(&value.as_str()))
.collect::<Vec<_>>();
resolved.sort();
resolved.dedup();
resolved
}
impl ResolvedProjectLayout {
pub fn platform_default() -> Self {
ZebflowJsonLayout::default().resolve()
}
pub fn source_rel(&self, rest: &str) -> String {
if rest.is_empty() {
self.source.clone()
} else if self.source.is_empty() {
rest.to_string()
} else {
format!("{}/{rest}", self.source)
}
}
pub fn strip_source<'a>(&self, rel: &'a str) -> Option<&'a str> {
strip_dir_prefix(&self.source, rel)
}
pub fn is_in_source(&self, rel: &str) -> bool {
self.strip_source(rel).is_some()
}
pub fn is_pipeline_rel_path(&self, rel: &str) -> bool {
self.is_in_source(rel) && rel.ends_with(PIPELINE_DEFINITION_EXTENSION)
}
pub fn is_schema_rel_path(&self, rel: &str) -> bool {
strip_dir_prefix(&self.schema, rel).is_some()
}
pub fn schema_document_rel(&self) -> String {
format!("{}/{SCHEMA_DOCUMENT_FILE}", self.schema)
}
pub fn is_sqlite_schema_rel_path(&self, rel: &str) -> bool {
strip_dir_prefix(&self.sqlite_schema, rel).is_some()
}
pub fn sqlite_schema_document_rel(&self) -> String {
format!("{}/{SQLITE_SCHEMA_DOCUMENT_FILE}", self.sqlite_schema)
}
pub fn initial_data_engine(&self, rel: &str) -> Option<&str> {
if !rel.ends_with(".sql") {
return None;
}
self.initial_data
.iter()
.find(|dir| strip_dir_prefix(&dir.path, rel).is_some())
.map(|dir| dir.engine.as_str())
}
pub fn refused_file_type(&self, rel: &str) -> Option<String> {
let name = rel.rsplit('/').next().unwrap_or(rel);
if ALWAYS_ALLOWED_FILE_NAMES.contains(&name.to_ascii_lowercase().as_str()) {
return None;
}
match file_extension(name) {
Some(extension)
if self
.allowed_extensions
.iter()
.any(|item| *item == extension) =>
{
None
}
Some(extension) => Some(format!("extension '.{extension}'")),
None => Some("a name with no extension".to_string()),
}
}
}
fn file_extension(name: &str) -> Option<String> {
let (stem, extension) = name.rsplit_once('.')?;
if stem.is_empty() || extension.is_empty() {
return None;
}
Some(extension.to_ascii_lowercase())
}
pub fn strip_dir_prefix<'a>(dir: &str, rel: &'a str) -> Option<&'a str> {
if dir.is_empty() {
return Some(rel);
}
rel.strip_prefix(dir)?.strip_prefix('/')
}
impl ZebflowJson {
pub fn layout(&self) -> ResolvedProjectLayout {
self.configs.layout.resolve()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ZebflowJsonPipelines {
#[serde(default)]
pub logging: ZebflowJsonLogging,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub node_timeout_secs: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ZebflowJsonData {}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ZebflowJsonFiles {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub backend: Option<String>,
#[serde(default)]
pub uploads: ZebflowJsonUploads,
}
impl ZebflowJsonFiles {
pub fn effective_backend(&self) -> Result<FileBackend, ZebFsError> {
FileBackend::resolve(self.backend.as_deref())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
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, MAX_UPLOAD_SIZE_MB)
}
pub fn effective_webhook_body_max_mb(&self) -> u32 {
self.webhook_body_max_mb
.clamp(default_webhook_body_max_mb(), MAX_UPLOAD_SIZE_MB)
}
pub fn effective_max_file_size_mb(&self) -> u32 {
self.max_file_size_mb
.unwrap_or(MAX_UPLOAD_SIZE_MB)
.clamp(5, MAX_UPLOAD_SIZE_MB)
}
}
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)]
#[serde(deny_unknown_fields)]
pub struct ZebflowJsonDistribution {
#[serde(default)]
pub hub: ZebflowJsonDistributionHub,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ZebflowJsonDistributionHub {
#[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)]
#[serde(deny_unknown_fields)]
pub struct ZebflowJsonLocks {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub templates: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ZebflowJsonGit {
#[serde(default)]
pub remote: ZebflowJsonGitRemote,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
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)]
#[serde(deny_unknown_fields)]
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>,
}
impl Default for ZebflowJsonRwe {
fn default() -> Self {
Self {
allow_list: Vec::new(),
minify_html: false,
strict_mode: default_rwe_strict_mode(),
libraries: HashMap::new(),
deployment_asset_base: None,
}
}
}
pub type ZebflowJsonRweLibraries = HashMap<String, ZebflowJsonRweLibraryEntry>;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ZebflowJsonRweLibraryEntry {
pub version: String,
pub source: 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)]
#[serde(deny_unknown_fields)]
pub struct ZebflowJsonLogging {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_invocations: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub trace_capture: Option<crate::pipeline::trace_capture::TraceCaptureSettings>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_error_groups: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub occurrence_ring: Option<u32>,
}
impl ZebflowJsonLogging {
pub fn effective_max_invocations(&self) -> usize {
self.max_invocations.unwrap_or(20).max(1) as usize
}
pub fn error_group_bounds(&self) -> ErrorGroupBounds {
let d = ErrorGroupBounds::default();
ErrorGroupBounds {
max_groups: self.max_error_groups.map(|v| v.max(1) as usize).unwrap_or(d.max_groups),
occurrence_ring: self.occurrence_ring.map(|v| v.max(1) as usize).unwrap_or(d.occurrence_ring),
..d
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PipelineErrorGroup {
pub file_rel_path: String,
pub signature: String,
pub node_id: String,
pub code: String,
pub message_pattern: String,
pub count: u64,
pub first_seen: i64,
pub last_seen: i64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reopened_at: Option<i64>,
pub first: PipelineInvocationEntry,
pub latest: PipelineInvocationEntry,
pub occurrences: Vec<(String, i64)>,
pub capture: String,
#[serde(default)]
pub captured: Vec<PipelineInvocationEntry>,
}
pub fn failing_trace_entry(
trace: &[crate::pipeline::model::NodeTraceEntry],
) -> Option<&crate::pipeline::model::NodeTraceEntry> {
trace
.iter()
.rev()
.find(|t| t.error.is_some() && t.status != "retry" && t.status != "error_routed")
}
pub fn error_group_parts(entry: &PipelineInvocationEntry) -> (String, String, String) {
if let Some(t) = failing_trace_entry(&entry.trace) {
let err = t.error.clone().unwrap_or_default();
let (code, message) = match err.split_once(": ") {
Some((c, m)) if c.len() < 48 && c.chars().all(|ch| ch.is_ascii_uppercase() || ch == '_' || ch.is_ascii_digit()) => (c.to_string(), m.to_string()),
_ => (String::new(), err),
};
return (t.node_id.clone(), code, message);
}
(String::new(), String::new(), entry.error.clone().unwrap_or_default())
}
pub fn error_message_pattern(message: &str) -> String {
let mut out = String::with_capacity(message.len());
let mut chars = message.chars().peekable();
let mut in_quote: Option<char> = None;
while let Some(c) = chars.next() {
if let Some(q) = in_quote {
if c == q {
in_quote = None;
out.push(q);
}
continue;
}
match c {
'"' | '\'' | '`' => {
in_quote = Some(c);
out.push(c);
out.push('?');
}
d if d.is_ascii_digit() => {
if !out.ends_with('#') {
out.push('#');
}
while chars.peek().is_some_and(|n| n.is_ascii_digit()) {
chars.next();
}
}
other => out.push(other),
}
}
out.chars().take(200).collect()
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub struct ErrorGroupBounds {
pub max_groups: usize,
pub occurrence_ring: usize,
pub capture_cap: usize,
pub reopen_after_secs: i64,
}
impl Default for ErrorGroupBounds {
fn default() -> Self {
Self { max_groups: 200, occurrence_ring: 500, capture_cap: 200, reopen_after_secs: 7 * 24 * 3600 }
}
}
#[derive(Debug, Clone, Copy)]
pub struct EffectivePipelineInvocationRetention {
pub max_invocations: usize,
pub max_age_secs: Option<i64>,
}
pub fn resolve_invocation_retention(
project_cfg: &ZebflowJson,
graph: Option<&crate::pipeline::PipelineGraph>,
) -> EffectivePipelineInvocationRetention {
let project_max_invocations = project_cfg
.configs
.pipelines
.logging
.effective_max_invocations();
let pipeline_retention = graph
.and_then(|graph| graph.metadata.as_ref())
.and_then(|metadata| metadata.settings.invocation_retention.as_ref());
let max_invocations = pipeline_retention
.and_then(|retention| retention.max_invocations)
.map(|value| value.max(1) as usize)
.unwrap_or(project_max_invocations);
let max_age_secs = pipeline_retention
.and_then(|retention| retention.max_age_secs)
.map(|value| value.max(1) as i64);
EffectivePipelineInvocationRetention {
max_invocations,
max_age_secs,
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
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 PipelineInvocationLogPipelineStats {
pub file_rel_path: String,
pub count: u64,
pub trace_bytes: u64,
pub largest_trace_bytes: u64,
pub latest_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PipelineInvocationLogStats {
pub count: u64,
pub trace_bytes: u64,
pub db_bytes: u64,
pub wal_bytes: u64,
pub shm_bytes: u64,
pub pipelines: Vec<PipelineInvocationLogPipelineStats>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
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, 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,
#[serde(default)]
pub nonce: String,
}
#[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 ClusterWorkerRegisterResponse {
pub ok: bool,
pub worker: crate::infra::cluster::registry::WorkerRegistryRecord,
#[serde(default)]
pub proof: String,
}
#[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 ClusterJoinTokenMintRequest {
pub office_id: String,
#[serde(default)]
pub label: String,
#[serde(default)]
pub base_url: String,
#[serde(default)]
pub note: String,
#[serde(default)]
pub rotate: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ClusterMintedJoinToken {
pub record: PlatformOfficeJoinToken,
pub office: PlatformOffice,
pub token: String,
}
#[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,
Full,
}
impl ProjectTransferArtifactKind {
pub fn key(self) -> &'static str {
match self {
Self::Bundle => "bundle",
Self::Files => "files",
Self::Full => "full",
}
}
pub fn archive_name(self) -> &'static str {
match self {
Self::Bundle => "project.bundle.tar",
Self::Files => "project.files.tar",
Self::Full => "project.full.tar",
}
}
pub fn classes(self) -> &'static [crate::contracts::kinds::ProjectBundleClass] {
use crate::contracts::kinds::ProjectBundleClass;
match self {
Self::Bundle => &[ProjectBundleClass::Repo, ProjectBundleClass::Store],
Self::Files => &[ProjectBundleClass::Files],
Self::Full => &[
ProjectBundleClass::Repo,
ProjectBundleClass::Store,
ProjectBundleClass::Files,
],
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ProjectOperationKind {
ExportBundle,
ExportFiles,
ImportBundle,
ImportFiles,
ExportFull,
ImportFull,
PlatformImport,
RollbackImport,
}
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",
Self::ExportFull => "export_full",
Self::ImportFull => "import_full",
Self::PlatformImport => "platform_import",
Self::RollbackImport => "rollback_import",
}
}
}
#[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 ChangePasswordRequest {
pub current_password: String,
pub new_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 recovery_date_stamp() -> String {
chrono::Utc::now().format("%Y-%m-%d").to_string()
}
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_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),
"git_command" => Some(ProjectCapability::PipelinesWrite),
"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),
"file_list" => Some(ProjectCapability::TemplatesRead),
"file_read" => Some(ProjectCapability::TemplatesRead),
"file_create" => Some(ProjectCapability::TemplatesCreate),
"file_write" => Some(ProjectCapability::TemplatesWrite),
"file_search" => Some(ProjectCapability::TemplatesRead),
"file_edit" => Some(ProjectCapability::TemplatesWrite),
"file_outline" => Some(ProjectCapability::TemplatesRead),
"file_deps" => Some(ProjectCapability::TemplatesRead),
"file_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_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),
"skill_list" => Some(ProjectCapability::ProjectRead),
"skill_read" => Some(ProjectCapability::ProjectRead),
"list_ui_catalog" => Some(ProjectCapability::PipelinesRead),
"install_ui_components" => Some(ProjectCapability::PipelinesWrite),
"hub_search" => Some(ProjectCapability::PipelinesRead),
"hub_review" => Some(ProjectCapability::PipelinesRead),
"hub_add" => Some(ProjectCapability::PipelinesWrite),
"route_fetch" => Some(ProjectCapability::PipelinesExecute),
"theme_generate" => Some(ProjectCapability::ProjectRead),
"move_resource" => Some(ProjectCapability::PipelinesWrite),
_ => None,
}
}