operations: enforce community capability limits

This commit is contained in:
a.tolmachev
2026-05-03 17:55:35 +00:00
parent 1f0818d660
commit 82b7b7278e
22 changed files with 612 additions and 324 deletions
+6
View File
@@ -26,6 +26,12 @@ pub enum OperationSecurityLevel {
Strict,
}
impl Default for OperationSecurityLevel {
fn default() -> Self {
Self::Standard
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct EditionLimits {
pub max_workspaces: Option<u32>,
+13
View File
@@ -5,6 +5,7 @@ use serde_json::Value;
use time::OffsetDateTime;
use crate::{
edition::OperationSecurityLevel,
ids::{AuthProfileId, DescriptorId, OperationId, SampleId},
protocol::{ExportMode, GraphqlOperationType, HttpMethod, Protocol},
soap::{
@@ -17,6 +18,10 @@ fn default_operation_category() -> String {
"general".to_owned()
}
fn default_operation_security_level() -> OperationSecurityLevel {
OperationSecurityLevel::Standard
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum OperationStatus {
@@ -217,6 +222,8 @@ pub struct Operation<TSchema, TMapping> {
#[serde(default = "default_operation_category")]
pub category: String,
pub protocol: Protocol,
#[serde(default = "default_operation_security_level")]
pub security_level: OperationSecurityLevel,
pub status: OperationStatus,
pub version: u32,
pub target: Target,
@@ -272,6 +279,7 @@ mod tests {
use crate::{
auth::{AuthConfig, AuthProfile, BearerAuthConfig},
edition::OperationSecurityLevel,
ids::{AuthProfileId, OperationId, SampleId},
operation::{
ConfigExport, ExecutionConfig, GraphqlTarget, Operation, OperationStatus,
@@ -382,6 +390,7 @@ mod tests {
display_name: "Create Lead".to_owned(),
category: "sales".to_owned(),
protocol: Protocol::Rest,
security_level: OperationSecurityLevel::Standard,
status: OperationStatus::Draft,
version: 1,
target: Target::Rest(RestTarget {
@@ -470,6 +479,7 @@ mod tests {
display_name: "Create Lead".to_owned(),
category: "sales".to_owned(),
protocol: Protocol::Rest,
security_level: OperationSecurityLevel::Standard,
status: OperationStatus::Published,
version: 3,
target: Target::Rest(RestTarget {
@@ -514,6 +524,7 @@ mod tests {
serde_yaml::from_str(&yaml).unwrap();
assert!(yaml.contains("protocol: rest"));
assert!(yaml.contains("security_level: standard"));
assert!(yaml.contains("export_mode: portable"));
assert_eq!(restored, operation);
}
@@ -526,6 +537,7 @@ name: crm_create_lead
display_name: Create Lead
category: sales
protocol: rest
security_level: standard
status: draft
version: 1
target:
@@ -557,6 +569,7 @@ updated_at: 2026-03-25T08:10:00Z
let restored: Operation<serde_json::Value, serde_json::Value> =
serde_yaml::from_str(yaml).unwrap();
assert_eq!(restored.security_level, OperationSecurityLevel::Standard);
assert_eq!(restored.published_at, None);
}
}
+6
View File
@@ -198,6 +198,7 @@ pub async fn apply_postgres(pool: &PgPool) -> Result<(), sqlx::Error> {
display_name text not null,
category text not null default 'general',
protocol text not null,
security_level text not null default 'standard',
status text not null,
current_draft_version integer not null default 1,
latest_published_version integer null,
@@ -217,6 +218,11 @@ pub async fn apply_postgres(pool: &PgPool) -> Result<(), sqlx::Error> {
)
.execute(pool)
.await?;
query(
"alter table operations add column if not exists security_level text not null default 'standard'",
)
.execute(pool)
.await?;
query("update operations set workspace_id = 'ws_default' where workspace_id is null")
.execute(pool)
.await?;
+4 -3
View File
@@ -2,9 +2,9 @@ use crank_core::{
Agent, AgentId, AgentOperationBinding, AgentStatus, AgentVersion, AsyncJobHandle, AsyncJobId,
AuthProfile, DescriptorId, ExecutionMode, ExportMode, InvitationToken, InvocationLevel,
InvocationLog, InvocationSource, JobStatus, MembershipRole, Operation, OperationId,
OperationStatus, PlatformApiKey, Protocol, SampleId, Secret, SecretId, SecretVersion,
StreamSession, StreamSessionId, StreamStatus, UsagePeriod, UsageRollup, User, UserSessionId,
Workspace, WorkspaceId,
OperationSecurityLevel, OperationStatus, PlatformApiKey, Protocol, SampleId, Secret, SecretId,
SecretVersion, StreamSession, StreamSessionId, StreamStatus, UsagePeriod, UsageRollup, User,
UserSessionId, Workspace, WorkspaceId,
};
use crank_mapping::MappingSet;
use crank_schema::Schema;
@@ -207,6 +207,7 @@ pub struct OperationSummary {
pub display_name: String,
pub category: String,
pub protocol: Protocol,
pub security_level: OperationSecurityLevel,
pub target_url: String,
pub target_action: String,
pub status: OperationStatus,
@@ -452,6 +452,7 @@ impl PostgresRegistry {
o.display_name,
o.category,
o.protocol,
o.security_level,
o.created_at as \"operation_created_at!: time::OffsetDateTime\",
o.updated_at as \"operation_updated_at!: time::OffsetDateTime\",
o.published_at as \"operation_published_at: time::OffsetDateTime\",
@@ -523,6 +524,7 @@ impl PostgresRegistry {
row.display_name,
row.category,
row.protocol,
row.security_level,
row.operation_created_at,
row.operation_updated_at,
row.operation_published_at,
+9 -4
View File
@@ -714,6 +714,7 @@ fn build_operation_summary(
display_name: String,
category: String,
protocol: String,
security_level: String,
target_json: Value,
status: String,
current_draft_version: i32,
@@ -732,6 +733,7 @@ fn build_operation_summary(
display_name,
category,
protocol: deserialize_enum_text(&protocol, "protocol")?,
security_level: deserialize_enum_text(&security_level, "security_level")?,
target_url,
target_action,
status: deserialize_enum_text(&status, "status")?,
@@ -831,6 +833,7 @@ fn build_operation_version_record(
display_name: String,
category: String,
protocol: String,
security_level: String,
operation_created_at: OffsetDateTime,
operation_updated_at: OffsetDateTime,
operation_published_at: Option<OffsetDateTime>,
@@ -868,6 +871,7 @@ fn build_operation_version_record(
display_name,
category,
protocol: deserialize_enum_text(&protocol, "protocol")?,
security_level: deserialize_enum_text(&security_level, "security_level")?,
status,
version,
target: deserialize_json_value(target_json)?,
@@ -1130,10 +1134,10 @@ mod tests {
AgentId, AgentOperationBinding, AgentStatus, AgentVersion, ApiKeyHeaderAuthConfig,
AsyncJobHandle, AuthConfig, AuthKind, AuthProfile, ConfigExport, ExecutionConfig,
ExportMode, GeneratedDraft, GeneratedDraftStatus, HttpMethod, InvocationLog, JobStatus,
MembershipRole, OperationId, OperationStatus, PlatformApiKey, PlatformApiKeyId,
PlatformApiKeyScope, PlatformApiKeyStatus, Protocol, RestTarget, RetryPolicy, Samples,
SecretId, StreamSession, StreamSessionId, StreamStatus, Target, ToolDescription,
ToolExample, User, UserId, UserSessionId, Workspace, WorkspaceId,
MembershipRole, OperationId, OperationSecurityLevel, OperationStatus, PlatformApiKey,
PlatformApiKeyId, PlatformApiKeyScope, PlatformApiKeyStatus, Protocol, RestTarget,
RetryPolicy, Samples, SecretId, StreamSession, StreamSessionId, StreamStatus, Target,
ToolDescription, ToolExample, User, UserId, UserSessionId, Workspace, WorkspaceId,
};
use crank_mapping::{MappingRule, MappingSet};
use crank_schema::{Schema, SchemaKind};
@@ -2345,6 +2349,7 @@ mod tests {
display_name: format!("Display {id}"),
category: "general".to_owned(),
protocol: Protocol::Rest,
security_level: OperationSecurityLevel::Standard,
status,
version,
target: Target::Rest(RestTarget {
@@ -34,6 +34,7 @@ impl PostgresRegistry {
display_name,
category,
protocol,
security_level,
status,
current_draft_version,
latest_published_version,
@@ -41,10 +42,10 @@ impl PostgresRegistry {
updated_at,
published_at
) values (
$1, $2, $3, $4, $5, $6, $7, $8, $9,
$10::timestamptz,
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10,
$11::timestamptz,
$12::timestamptz
$12::timestamptz,
$13::timestamptz
)",
)
.bind(snapshot.id.as_str())
@@ -53,6 +54,10 @@ impl PostgresRegistry {
.bind(&snapshot.display_name)
.bind(&snapshot.category)
.bind(serialize_enum_text(&snapshot.protocol, "protocol")?)
.bind(serialize_enum_text(
&snapshot.security_level,
"security_level",
)?)
.bind(serialize_enum_text(&snapshot.status, "status")?)
.bind(to_db_version(snapshot.version))
.bind(
@@ -155,12 +160,17 @@ impl PostgresRegistry {
"update operations
set display_name = $1,
category = $2,
status = $3,
updated_at = $4::timestamptz
where workspace_id = $5 and id = $6",
security_level = $3,
status = $4,
updated_at = $5::timestamptz
where workspace_id = $6 and id = $7",
)
.bind(&snapshot.display_name)
.bind(&snapshot.category)
.bind(serialize_enum_text(
&snapshot.security_level,
"security_level",
)?)
.bind(serialize_enum_text(&snapshot.status, "status")?)
.bind(snapshot.updated_at)
.bind(workspace_id.as_str())
@@ -316,6 +326,7 @@ impl PostgresRegistry {
operations.display_name,
operations.category,
operations.protocol,
operations.security_level,
ov.target_json,
operations.status,
operations.current_draft_version,
@@ -343,6 +354,7 @@ impl PostgresRegistry {
row.display_name,
row.category,
row.protocol,
row.security_level,
row.target_json,
row.status,
row.current_draft_version,
@@ -428,6 +440,7 @@ impl PostgresRegistry {
operations.display_name,
operations.category,
operations.protocol,
operations.security_level,
ov.target_json,
operations.status,
operations.current_draft_version,
@@ -454,6 +467,7 @@ impl PostgresRegistry {
row.display_name,
row.category,
row.protocol,
row.security_level,
row.target_json,
row.status,
row.current_draft_version,
@@ -480,6 +494,7 @@ impl PostgresRegistry {
o.display_name,
o.category,
o.protocol,
o.security_level,
o.created_at as \"operation_created_at!: time::OffsetDateTime\",
o.updated_at as \"operation_updated_at!: time::OffsetDateTime\",
o.published_at as \"operation_published_at: time::OffsetDateTime\",
@@ -516,6 +531,7 @@ impl PostgresRegistry {
row.display_name,
row.category,
row.protocol,
row.security_level,
row.operation_created_at,
row.operation_updated_at,
row.operation_published_at,
@@ -552,6 +568,7 @@ impl PostgresRegistry {
o.display_name,
o.category,
o.protocol,
o.security_level,
o.created_at as \"operation_created_at!: time::OffsetDateTime\",
o.updated_at as \"operation_updated_at!: time::OffsetDateTime\",
o.published_at as \"operation_published_at: time::OffsetDateTime\",
@@ -589,6 +606,7 @@ impl PostgresRegistry {
row.display_name,
row.category,
row.protocol,
row.security_level,
row.operation_created_at,
row.operation_updated_at,
row.operation_published_at,
@@ -691,6 +709,7 @@ impl PostgresRegistry {
o.display_name,
o.category,
o.protocol,
o.security_level,
o.created_at as \"operation_created_at!: time::OffsetDateTime\",
o.updated_at as \"operation_updated_at!: time::OffsetDateTime\",
o.published_at as \"operation_published_at: time::OffsetDateTime\",
@@ -727,6 +746,7 @@ impl PostgresRegistry {
row.display_name,
row.category,
row.protocol,
row.security_level,
row.operation_created_at,
row.operation_updated_at,
row.operation_published_at,
@@ -760,6 +780,7 @@ impl PostgresRegistry {
o.display_name,
o.category,
o.protocol,
o.security_level,
o.created_at as \"operation_created_at!: time::OffsetDateTime\",
o.updated_at as \"operation_updated_at!: time::OffsetDateTime\",
o.published_at as \"operation_published_at: time::OffsetDateTime\",
@@ -796,6 +817,7 @@ impl PostgresRegistry {
row.display_name,
row.category,
row.protocol,
row.security_level,
row.operation_created_at,
row.operation_updated_at,
row.operation_published_at,
+10 -3
View File
@@ -718,9 +718,9 @@ mod tests {
use crank_core::{
AggregationMode, DescriptorId, ExecutionConfig, ExecutionMode, GeneratedDraft,
GeneratedDraftStatus, GraphqlOperationType, GraphqlTarget, GrpcTarget, HttpMethod,
Operation, OperationId, OperationStatus, Protocol, ProtocolOptions, RestTarget, Samples,
SoapBindingStyle, SoapOperationMetadata, SoapTarget, SoapVersion, StreamingConfig, Target,
ToolDescription, ToolExample, ToolFamilyConfig, TransportBehavior,
Operation, OperationId, OperationSecurityLevel, OperationStatus, Protocol, ProtocolOptions,
RestTarget, Samples, SoapBindingStyle, SoapOperationMetadata, SoapTarget, SoapVersion,
StreamingConfig, Target, ToolDescription, ToolExample, ToolFamilyConfig, TransportBehavior,
WebsocketProtocolOptions, WebsocketTarget,
};
use crank_mapping::{MappingRule, MappingSet};
@@ -1654,6 +1654,7 @@ mod tests {
display_name: "Create Lead".to_owned(),
category: "sales".to_owned(),
protocol: Protocol::Rest,
security_level: OperationSecurityLevel::Standard,
status: OperationStatus::Published,
version: 1,
target: Target::Rest(RestTarget {
@@ -1717,6 +1718,7 @@ mod tests {
display_name: "Create Lead GraphQL".to_owned(),
category: "sales".to_owned(),
protocol: Protocol::Graphql,
security_level: OperationSecurityLevel::Standard,
status: OperationStatus::Published,
version: 1,
target: Target::Graphql(GraphqlTarget {
@@ -1793,6 +1795,7 @@ mod tests {
display_name: "Unary Echo gRPC".to_owned(),
category: "support".to_owned(),
protocol: Protocol::Grpc,
security_level: OperationSecurityLevel::Standard,
status: OperationStatus::Published,
version: 1,
target: Target::Grpc(GrpcTarget {
@@ -1872,6 +1875,7 @@ mod tests {
display_name: "Server Stream Echo gRPC".to_owned(),
category: "support".to_owned(),
protocol: Protocol::Grpc,
security_level: OperationSecurityLevel::Standard,
status: OperationStatus::Published,
version: 1,
target: Target::Grpc(GrpcTarget {
@@ -1950,6 +1954,7 @@ mod tests {
display_name: "Create Lead SOAP".to_owned(),
category: "sales".to_owned(),
protocol: Protocol::Soap,
security_level: OperationSecurityLevel::Standard,
status: OperationStatus::Published,
version: 1,
target: Target::Soap(SoapTarget {
@@ -2039,6 +2044,7 @@ mod tests {
display_name: "Billing Log Window".to_owned(),
category: "ops".to_owned(),
protocol: Protocol::Rest,
security_level: OperationSecurityLevel::Standard,
status: OperationStatus::Published,
version: 1,
target: Target::Rest(RestTarget {
@@ -2139,6 +2145,7 @@ mod tests {
display_name: "Telemetry Window WebSocket".to_owned(),
category: "ops".to_owned(),
protocol: Protocol::Websocket,
security_level: OperationSecurityLevel::Standard,
status: OperationStatus::Published,
version: 1,
target: Target::Websocket(WebsocketTarget {