operations: enforce community capability limits
This commit is contained in:
+14
-8
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "select\n operations.id,\n operations.workspace_id,\n operations.name,\n operations.display_name,\n operations.category,\n operations.protocol,\n ov.target_json,\n operations.status,\n operations.current_draft_version,\n operations.latest_published_version,\n operations.created_at as \"created_at!: time::OffsetDateTime\",\n operations.updated_at as \"updated_at!: time::OffsetDateTime\",\n operations.published_at as \"published_at: time::OffsetDateTime\"\n from operations\n join operation_versions ov\n on ov.operation_id = operations.id\n and ov.version = operations.current_draft_version\n where operations.workspace_id = $1\n order by operations.name asc",
|
||||
"query": "select\n operations.id,\n operations.workspace_id,\n operations.name,\n operations.display_name,\n operations.category,\n operations.protocol,\n operations.security_level,\n ov.target_json,\n operations.status,\n operations.current_draft_version,\n operations.latest_published_version,\n operations.created_at as \"created_at!: time::OffsetDateTime\",\n operations.updated_at as \"updated_at!: time::OffsetDateTime\",\n operations.published_at as \"published_at: time::OffsetDateTime\"\n from operations\n join operation_versions ov\n on ov.operation_id = operations.id\n and ov.version = operations.current_draft_version\n where operations.workspace_id = $1\n order by operations.name asc",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -35,36 +35,41 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "security_level",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "target_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 8,
|
||||
"name": "status",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"name": "current_draft_version",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "latest_published_version",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "created_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "updated_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "published_at: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
@@ -84,11 +89,12 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "e4392329cd965b3f5100b38fb26b828f1bce4e6c9951ed6f4701d52cab3b7de5"
|
||||
"hash": "0b8e9bed137684683d8b6a9ed52c94b6fb3bbcb19c7a621c78c5a6d3698bdf4d"
|
||||
}
|
||||
+25
-19
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "select\n o.id,\n o.workspace_id,\n o.name,\n o.display_name,\n o.category,\n o.protocol,\n o.created_at as \"operation_created_at!: time::OffsetDateTime\",\n o.updated_at as \"operation_updated_at!: time::OffsetDateTime\",\n o.published_at as \"operation_published_at: time::OffsetDateTime\",\n ov.version,\n ov.status,\n ov.target_json,\n ov.input_schema_json,\n ov.output_schema_json,\n ov.input_mapping_json,\n ov.output_mapping_json,\n ov.execution_config_json,\n ov.tool_description_json,\n ov.samples_json,\n ov.generated_draft_json,\n ov.config_export_json,\n ov.change_note,\n ov.created_at as \"created_at!: time::OffsetDateTime\",\n ov.created_by\n from operation_versions ov\n join operations o on o.id = ov.operation_id\n where o.workspace_id = $1 and ov.operation_id = $2\n order by ov.version asc",
|
||||
"query": "select\n o.id,\n o.workspace_id,\n o.name,\n o.display_name,\n o.category,\n o.protocol,\n o.security_level,\n o.created_at as \"operation_created_at!: time::OffsetDateTime\",\n o.updated_at as \"operation_updated_at!: time::OffsetDateTime\",\n o.published_at as \"operation_published_at: time::OffsetDateTime\",\n ov.version,\n ov.status,\n ov.target_json,\n ov.input_schema_json,\n ov.output_schema_json,\n ov.input_mapping_json,\n ov.output_mapping_json,\n ov.execution_config_json,\n ov.tool_description_json,\n ov.samples_json,\n ov.generated_draft_json,\n ov.config_export_json,\n ov.change_note,\n ov.created_at as \"created_at!: time::OffsetDateTime\",\n ov.created_by\n from operation_versions ov\n join operations o on o.id = ov.operation_id\n where o.workspace_id = $1 and ov.operation_id = $2\n order by ov.version asc",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -35,91 +35,96 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "security_level",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "operation_created_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 8,
|
||||
"name": "operation_updated_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"name": "operation_published_at: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "version",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "status",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "target_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "input_schema_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "output_schema_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "input_mapping_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 16,
|
||||
"name": "output_mapping_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 17,
|
||||
"name": "execution_config_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 18,
|
||||
"name": "tool_description_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 19,
|
||||
"name": "samples_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 20,
|
||||
"name": "generated_draft_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 21,
|
||||
"name": "config_export_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 22,
|
||||
"name": "change_note",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 23,
|
||||
"name": "created_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 24,
|
||||
"name": "created_by",
|
||||
"type_info": "Text"
|
||||
}
|
||||
@@ -139,6 +144,7 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
@@ -157,5 +163,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "eeec56487c44b28624364e17778451924b910c56d7be7da9a361f74de9d9d546"
|
||||
"hash": "10ac4260e678ac458750988f95e54d3a84d9870e10e86bbe0d9c9fd99bf8a1f5"
|
||||
}
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "select\n k.id,\n k.workspace_id,\n k.name,\n k.prefix,\n k.scopes_json,\n k.status,\n k.created_at as \"created_at!: time::OffsetDateTime\",\n k.last_used_at as \"last_used_at: time::OffsetDateTime\"\n from platform_api_keys k\n join workspaces w on w.id = k.workspace_id\n where w.slug = $1\n and k.secret_hash = $2\n and k.status = 'active'\n limit 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "prefix",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "scopes_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "status",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "created_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "last_used_at: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "213432827436e3a17fb2c17e392c1f8d7aa7b7624c64185fe0d19a3b2bd7d713"
|
||||
}
|
||||
+25
-19
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "select\n o.id,\n o.workspace_id,\n o.name,\n o.display_name,\n o.category,\n o.protocol,\n o.created_at as \"operation_created_at!: time::OffsetDateTime\",\n o.updated_at as \"operation_updated_at!: time::OffsetDateTime\",\n o.published_at as \"operation_published_at: time::OffsetDateTime\",\n ov.version,\n ov.status,\n ov.target_json,\n ov.input_schema_json,\n ov.output_schema_json,\n ov.input_mapping_json,\n ov.output_mapping_json,\n ov.execution_config_json,\n ov.tool_description_json,\n ov.samples_json,\n ov.generated_draft_json,\n ov.config_export_json,\n ov.change_note,\n ov.created_at as \"created_at!: time::OffsetDateTime\",\n ov.created_by\n from published_operations po\n join operation_versions ov\n on ov.operation_id = po.operation_id and ov.version = po.version\n join operations o on o.id = po.operation_id\n order by o.name asc",
|
||||
"query": "select\n o.id,\n o.workspace_id,\n o.name,\n o.display_name,\n o.category,\n o.protocol,\n o.security_level,\n o.created_at as \"operation_created_at!: time::OffsetDateTime\",\n o.updated_at as \"operation_updated_at!: time::OffsetDateTime\",\n o.published_at as \"operation_published_at: time::OffsetDateTime\",\n ov.version,\n ov.status,\n ov.target_json,\n ov.input_schema_json,\n ov.output_schema_json,\n ov.input_mapping_json,\n ov.output_mapping_json,\n ov.execution_config_json,\n ov.tool_description_json,\n ov.samples_json,\n ov.generated_draft_json,\n ov.config_export_json,\n ov.change_note,\n ov.created_at as \"created_at!: time::OffsetDateTime\",\n ov.created_by\n from published_operations po\n join operation_versions ov\n on ov.operation_id = po.operation_id and ov.version = po.version\n join operations o on o.id = po.operation_id\n order by o.name asc",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -35,91 +35,96 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "security_level",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "operation_created_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 8,
|
||||
"name": "operation_updated_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"name": "operation_published_at: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "version",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "status",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "target_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "input_schema_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "output_schema_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "input_mapping_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 16,
|
||||
"name": "output_mapping_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 17,
|
||||
"name": "execution_config_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 18,
|
||||
"name": "tool_description_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 19,
|
||||
"name": "samples_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 20,
|
||||
"name": "generated_draft_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 21,
|
||||
"name": "config_export_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 22,
|
||||
"name": "change_note",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 23,
|
||||
"name": "created_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 24,
|
||||
"name": "created_by",
|
||||
"type_info": "Text"
|
||||
}
|
||||
@@ -136,6 +141,7 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
@@ -154,5 +160,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "8ac15e73f704021c8cd23b3d32ebb215caa6eed07b30f4b3f98b97698e7334ee"
|
||||
"hash": "67a73cb5211a0c23d2e8d8361a8e37c488eb2420207e9c4fa682993eda20cd62"
|
||||
}
|
||||
+25
-19
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "select\n o.id,\n o.workspace_id,\n o.name,\n o.display_name,\n o.category,\n o.protocol,\n o.created_at as \"operation_created_at!: time::OffsetDateTime\",\n o.updated_at as \"operation_updated_at!: time::OffsetDateTime\",\n o.published_at as \"operation_published_at: time::OffsetDateTime\",\n ov.version,\n ov.status,\n ov.target_json,\n ov.input_schema_json,\n ov.output_schema_json,\n ov.input_mapping_json,\n ov.output_mapping_json,\n ov.execution_config_json,\n ov.tool_description_json,\n ov.samples_json,\n ov.generated_draft_json,\n ov.config_export_json,\n ov.change_note,\n ov.created_at as \"created_at!: time::OffsetDateTime\",\n ov.created_by\n from published_operations po\n join operation_versions ov\n on ov.operation_id = po.operation_id and ov.version = po.version\n join operations o on o.id = po.operation_id\n where po.operation_id = $1",
|
||||
"query": "select\n o.id,\n o.workspace_id,\n o.name,\n o.display_name,\n o.category,\n o.protocol,\n o.security_level,\n o.created_at as \"operation_created_at!: time::OffsetDateTime\",\n o.updated_at as \"operation_updated_at!: time::OffsetDateTime\",\n o.published_at as \"operation_published_at: time::OffsetDateTime\",\n ov.version,\n ov.status,\n ov.target_json,\n ov.input_schema_json,\n ov.output_schema_json,\n ov.input_mapping_json,\n ov.output_mapping_json,\n ov.execution_config_json,\n ov.tool_description_json,\n ov.samples_json,\n ov.generated_draft_json,\n ov.config_export_json,\n ov.change_note,\n ov.created_at as \"created_at!: time::OffsetDateTime\",\n ov.created_by\n from published_operations po\n join operation_versions ov\n on ov.operation_id = po.operation_id and ov.version = po.version\n join operations o on o.id = po.operation_id\n where po.operation_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -35,91 +35,96 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "security_level",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "operation_created_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 8,
|
||||
"name": "operation_updated_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"name": "operation_published_at: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "version",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "status",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "target_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "input_schema_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "output_schema_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "input_mapping_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 16,
|
||||
"name": "output_mapping_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 17,
|
||||
"name": "execution_config_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 18,
|
||||
"name": "tool_description_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 19,
|
||||
"name": "samples_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 20,
|
||||
"name": "generated_draft_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 21,
|
||||
"name": "config_export_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 22,
|
||||
"name": "change_note",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 23,
|
||||
"name": "created_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 24,
|
||||
"name": "created_by",
|
||||
"type_info": "Text"
|
||||
}
|
||||
@@ -138,6 +143,7 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
@@ -156,5 +162,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "4402c3661cf519344a81cfcc4e811e789b9a3d827825bc59f3a9cd0036665420"
|
||||
"hash": "7ad59ce20834c3de2b0345c1be28d84377b417efb30c7bdc38d4e3746a0c1a7a"
|
||||
}
|
||||
+25
-19
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "select\n o.id,\n o.workspace_id,\n o.name,\n o.display_name,\n o.category,\n o.protocol,\n o.created_at as \"operation_created_at!: time::OffsetDateTime\",\n o.updated_at as \"operation_updated_at!: time::OffsetDateTime\",\n o.published_at as \"operation_published_at: time::OffsetDateTime\",\n ov.version,\n ov.status,\n ov.target_json,\n ov.input_schema_json,\n ov.output_schema_json,\n ov.input_mapping_json,\n ov.output_mapping_json,\n ov.execution_config_json,\n ov.tool_description_json,\n ov.samples_json,\n ov.generated_draft_json,\n ov.config_export_json,\n ov.change_note,\n ov.created_at as \"created_at!: time::OffsetDateTime\",\n ov.created_by\n from operation_versions ov\n join operations o on o.id = ov.operation_id\n where o.workspace_id = $1 and ov.operation_id = $2 and ov.version = $3",
|
||||
"query": "select\n o.id,\n o.workspace_id,\n o.name,\n o.display_name,\n o.category,\n o.protocol,\n o.security_level,\n o.created_at as \"operation_created_at!: time::OffsetDateTime\",\n o.updated_at as \"operation_updated_at!: time::OffsetDateTime\",\n o.published_at as \"operation_published_at: time::OffsetDateTime\",\n ov.version,\n ov.status,\n ov.target_json,\n ov.input_schema_json,\n ov.output_schema_json,\n ov.input_mapping_json,\n ov.output_mapping_json,\n ov.execution_config_json,\n ov.tool_description_json,\n ov.samples_json,\n ov.generated_draft_json,\n ov.config_export_json,\n ov.change_note,\n ov.created_at as \"created_at!: time::OffsetDateTime\",\n ov.created_by\n from operation_versions ov\n join operations o on o.id = ov.operation_id\n where o.workspace_id = $1 and ov.operation_id = $2 and ov.version = $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -35,91 +35,96 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "security_level",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "operation_created_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 8,
|
||||
"name": "operation_updated_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"name": "operation_published_at: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "version",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "status",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "target_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "input_schema_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "output_schema_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "input_mapping_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 16,
|
||||
"name": "output_mapping_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 17,
|
||||
"name": "execution_config_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 18,
|
||||
"name": "tool_description_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 19,
|
||||
"name": "samples_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 20,
|
||||
"name": "generated_draft_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 21,
|
||||
"name": "config_export_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 22,
|
||||
"name": "change_note",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 23,
|
||||
"name": "created_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 24,
|
||||
"name": "created_by",
|
||||
"type_info": "Text"
|
||||
}
|
||||
@@ -140,6 +145,7 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
@@ -158,5 +164,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "40a5906682a4f90bb250c9b1809866e287f427fda9e28e0bfbff2c42231cf8b1"
|
||||
"hash": "a24c36f988151016f9ffa834a6a5658f7f094844b892c999e6f58d6f92fbe0c2"
|
||||
}
|
||||
+25
-19
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "select\n w.id as workspace_id,\n w.slug as workspace_slug,\n a.id as agent_id,\n a.slug as agent_slug,\n b.tool_name,\n b.tool_title,\n coalesce(b.tool_description_override, ov.tool_description_json->>'description') as \"tool_description!\",\n o.id,\n o.name,\n o.display_name,\n o.category,\n o.protocol,\n o.created_at as \"operation_created_at!: time::OffsetDateTime\",\n o.updated_at as \"operation_updated_at!: time::OffsetDateTime\",\n o.published_at as \"operation_published_at: time::OffsetDateTime\",\n ov.version,\n ov.status,\n ov.target_json,\n ov.input_schema_json,\n ov.output_schema_json,\n ov.input_mapping_json,\n ov.output_mapping_json,\n ov.execution_config_json,\n ov.tool_description_json,\n ov.samples_json,\n ov.generated_draft_json,\n ov.config_export_json,\n ov.change_note,\n ov.created_at as \"created_at!: time::OffsetDateTime\",\n ov.created_by\n from workspaces w\n join agents a on a.workspace_id = w.id\n join published_agents pa on pa.agent_id = a.id\n join agent_operation_bindings b on b.agent_id = a.id and b.agent_version = pa.version\n join operation_versions ov on ov.operation_id = b.operation_id and ov.version = b.operation_version\n join operations o on o.id = ov.operation_id and o.workspace_id = w.id\n where w.slug = $1 and a.slug = $2 and b.enabled = true\n order by b.tool_name asc",
|
||||
"query": "select\n w.id as workspace_id,\n w.slug as workspace_slug,\n a.id as agent_id,\n a.slug as agent_slug,\n b.tool_name,\n b.tool_title,\n coalesce(b.tool_description_override, ov.tool_description_json->>'description') as \"tool_description!\",\n o.id,\n o.name,\n o.display_name,\n o.category,\n o.protocol,\n o.security_level,\n o.created_at as \"operation_created_at!: time::OffsetDateTime\",\n o.updated_at as \"operation_updated_at!: time::OffsetDateTime\",\n o.published_at as \"operation_published_at: time::OffsetDateTime\",\n ov.version,\n ov.status,\n ov.target_json,\n ov.input_schema_json,\n ov.output_schema_json,\n ov.input_mapping_json,\n ov.output_mapping_json,\n ov.execution_config_json,\n ov.tool_description_json,\n ov.samples_json,\n ov.generated_draft_json,\n ov.config_export_json,\n ov.change_note,\n ov.created_at as \"created_at!: time::OffsetDateTime\",\n ov.created_by\n from workspaces w\n join agents a on a.workspace_id = w.id\n join published_agents pa on pa.agent_id = a.id\n join agent_operation_bindings b on b.agent_id = a.id and b.agent_version = pa.version\n join operation_versions ov on ov.operation_id = b.operation_id and ov.version = b.operation_version\n join operations o on o.id = ov.operation_id and o.workspace_id = w.id\n where w.slug = $1 and a.slug = $2 and b.enabled = true\n order by b.tool_name asc",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -65,91 +65,96 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "security_level",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "operation_created_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "operation_updated_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "operation_published_at: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 16,
|
||||
"name": "version",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 17,
|
||||
"name": "status",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 18,
|
||||
"name": "target_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 19,
|
||||
"name": "input_schema_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 20,
|
||||
"name": "output_schema_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 21,
|
||||
"name": "input_mapping_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 22,
|
||||
"name": "output_mapping_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 23,
|
||||
"name": "execution_config_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 24,
|
||||
"name": "tool_description_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"ordinal": 25,
|
||||
"name": "samples_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"ordinal": 26,
|
||||
"name": "generated_draft_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"ordinal": 27,
|
||||
"name": "config_export_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 27,
|
||||
"ordinal": 28,
|
||||
"name": "change_note",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"ordinal": 29,
|
||||
"name": "created_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"ordinal": 30,
|
||||
"name": "created_by",
|
||||
"type_info": "Text"
|
||||
}
|
||||
@@ -175,6 +180,7 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
@@ -193,5 +199,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "aa5a728d3d53aa0730497e9c75ccf1d385f00c6401ce0b17416b0009cf3f6ff3"
|
||||
"hash": "dd4a415a042b863b1034727737f3d14019ba405a07a8360b68e8c9c5597e17ce"
|
||||
}
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "select\n id,\n workspace_id,\n name,\n prefix,\n scopes_json,\n status,\n created_at as \"created_at!: time::OffsetDateTime\",\n last_used_at as \"last_used_at: time::OffsetDateTime\"\n from platform_api_keys\n where workspace_id = $1\n order by created_at desc",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "prefix",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "scopes_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "status",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "created_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "last_used_at: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "eeab4c7f8abeb26554b340cd5ed935573e1d3f0111fe9f61195bcfab87684959"
|
||||
}
|
||||
+14
-8
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "select\n operations.id,\n operations.workspace_id,\n operations.name,\n operations.display_name,\n operations.category,\n operations.protocol,\n ov.target_json,\n operations.status,\n operations.current_draft_version,\n operations.latest_published_version,\n operations.created_at as \"created_at!: time::OffsetDateTime\",\n operations.updated_at as \"updated_at!: time::OffsetDateTime\",\n operations.published_at as \"published_at: time::OffsetDateTime\"\n from operations\n join operation_versions ov\n on ov.operation_id = operations.id\n and ov.version = operations.current_draft_version\n where operations.workspace_id = $1 and operations.id = $2",
|
||||
"query": "select\n operations.id,\n operations.workspace_id,\n operations.name,\n operations.display_name,\n operations.category,\n operations.protocol,\n operations.security_level,\n ov.target_json,\n operations.status,\n operations.current_draft_version,\n operations.latest_published_version,\n operations.created_at as \"created_at!: time::OffsetDateTime\",\n operations.updated_at as \"updated_at!: time::OffsetDateTime\",\n operations.published_at as \"published_at: time::OffsetDateTime\"\n from operations\n join operation_versions ov\n on ov.operation_id = operations.id\n and ov.version = operations.current_draft_version\n where operations.workspace_id = $1 and operations.id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -35,36 +35,41 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "security_level",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "target_json",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 8,
|
||||
"name": "status",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"name": "current_draft_version",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "latest_published_version",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "created_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "updated_at!: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "published_at: time::OffsetDateTime",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
@@ -85,11 +90,12 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "1f20e68fd913f8585dc63f5044cb2aa2572e441a6655a08a9188bbacad893755"
|
||||
"hash": "f195cc4951ef129f4a1c95d6d9e8f932919d2882f3dcb5a7f865fb4448506d05"
|
||||
}
|
||||
@@ -2,49 +2,49 @@
|
||||
|
||||
## Current
|
||||
|
||||
### `feat/edition-capability-model`
|
||||
### `feat/community-surface-trim`
|
||||
|
||||
Status: in_progress
|
||||
|
||||
Goal:
|
||||
- ввести capability model по редакциям и перестать смешивать Community и premium surface.
|
||||
- привести открытую редакцию к честному product surface и убрать расхождение между текущим кодом и edition policy.
|
||||
|
||||
Main code areas:
|
||||
- `crates/crank-core/src/*`
|
||||
- `apps/admin-api/src/service.rs`
|
||||
- `apps/admin-api/src/app.rs`
|
||||
- `apps/ui/js/*`
|
||||
- `apps/ui/html/*`
|
||||
- `apps/ui/js/wizard.js`
|
||||
- `apps/ui/js/wizard-model.js`
|
||||
- `apps/ui/js/agents.js`
|
||||
- `apps/ui/js/settings.js`
|
||||
- `apps/ui/js/i18n.js`
|
||||
- `docs/product-editions.md`
|
||||
- `docs/frontend-roadmap.md`
|
||||
- `docs/architecture.md`
|
||||
- `docs/mcp-interface.md`
|
||||
|
||||
Implementation slices:
|
||||
1. Ввести серверную модель capabilities:
|
||||
- protocols
|
||||
- auth modes
|
||||
- security levels
|
||||
- workspace/user/agent limits
|
||||
2. Отдавать capability flags в `admin-api`.
|
||||
3. Привести UI к capability gating вместо скрытых продуктовых допущений.
|
||||
4. Зафиксировать Community defaults в docs и runtime config.
|
||||
1. Скрыть или честно заблокировать:
|
||||
- `WebSocket`
|
||||
- `SOAP`
|
||||
- `gRPC streaming`
|
||||
- streaming execution modes
|
||||
- `elevated/strict` security levels
|
||||
в Community.
|
||||
2. Привести copy и feature affordances к open-core границе.
|
||||
3. Добавить edition-aware explanation в wizard и agents UI.
|
||||
|
||||
Progress:
|
||||
- done: shared capability payload exists in `crank-core` and `admin-api`;
|
||||
- done: authenticated `/api/admin/capabilities` exposes Community defaults to UI;
|
||||
- done: first UI consumer uses capability payload in `Settings`;
|
||||
- done: `protocol-capabilities` is edition-aware and Community wizard hides premium protocol cards;
|
||||
- in_progress: surface Community-only security guidance inside wizard flow;
|
||||
- pending: add server-side validation so unsupported protocol and security-level drafts are rejected, not only hidden in UI.
|
||||
- done: Community wizard protocol list hides premium protocol cards;
|
||||
- done: Community security note is shown inside wizard flow;
|
||||
- done: server-side Community enforcement rejects unsupported protocols and premium security levels;
|
||||
- pending: убрать или честно заблокировать оставшиеся Community-incompatible streaming affordances;
|
||||
- pending: привести Agents/Settings copy к окончательной open-core границе.
|
||||
|
||||
DoD:
|
||||
- UI знает, какие функции входят в текущую редакцию;
|
||||
- Community не показывает рабочие controls для premium-only features;
|
||||
- capability checks существуют не только в UI, но и на сервере.
|
||||
- Community UX не создает ложного ожидания наличия premium functionality;
|
||||
- protocol set и security levels совпадают в docs, UI и runtime contracts.
|
||||
|
||||
Verification:
|
||||
- API tests for capability payloads;
|
||||
- frontend smoke for hidden/locked states;
|
||||
- manual verification on Community build.
|
||||
- e2e checks for wizard protocol list;
|
||||
- manual Community smoke;
|
||||
- localized copy review.
|
||||
|
||||
## Next
|
||||
|
||||
@@ -83,43 +83,6 @@ Verification:
|
||||
- unit tests for credential-kind enforcement;
|
||||
- docs sync check.
|
||||
|
||||
### `feat/community-surface-trim`
|
||||
|
||||
Status: ready
|
||||
|
||||
Goal:
|
||||
- привести открытую редакцию к честному product surface и убрать расхождение между текущим кодом и edition policy.
|
||||
|
||||
Main code areas:
|
||||
- `apps/ui/js/wizard.js`
|
||||
- `apps/ui/js/wizard-model.js`
|
||||
- `apps/ui/js/agents.js`
|
||||
- `apps/ui/js/settings.js`
|
||||
- `apps/ui/js/i18n.js`
|
||||
- `docs/product-editions.md`
|
||||
- `docs/architecture.md`
|
||||
- `docs/mcp-interface.md`
|
||||
|
||||
Implementation slices:
|
||||
1. Скрыть или честно заблокировать:
|
||||
- `WebSocket`
|
||||
- `SOAP`
|
||||
- `gRPC streaming`
|
||||
- streaming execution modes
|
||||
- `elevated/strict` security levels
|
||||
в Community.
|
||||
2. Привести copy и feature affordances к open-core границе.
|
||||
3. Добавить edition-aware explanation в wizard и agents UI.
|
||||
|
||||
DoD:
|
||||
- Community UX не создает ложного ожидания наличия premium functionality;
|
||||
- protocol set и security levels совпадают в docs, UI и runtime contracts.
|
||||
|
||||
Verification:
|
||||
- e2e checks for wizard protocol list;
|
||||
- manual Community smoke;
|
||||
- localized copy review.
|
||||
|
||||
## Planned
|
||||
|
||||
### `feat/frontend-commercial-polish`
|
||||
|
||||
+286
-3
@@ -233,9 +233,9 @@ mod tests {
|
||||
use crank_core::{
|
||||
AsyncJobHandle, AsyncJobId, DescriptorId, ExecutionConfig, ExecutionMode,
|
||||
GraphqlOperationType, GraphqlTarget, GrpcTarget, HttpMethod, JobStatus, MembershipRole,
|
||||
OperationId, Protocol, RestTarget, SecretKind, SoapBindingStyle, SoapOperationMetadata,
|
||||
SoapTarget, SoapVersion, StreamSession, StreamStatus, Target, ToolDescription,
|
||||
TransportBehavior, WorkspaceId,
|
||||
OperationId, OperationSecurityLevel, Protocol, RestTarget, SecretKind, SoapBindingStyle,
|
||||
SoapOperationMetadata, SoapTarget, SoapVersion, StreamSession, StreamStatus, Target,
|
||||
ToolDescription, TransportBehavior, WebsocketTarget, WorkspaceId,
|
||||
};
|
||||
use crank_mapping::{MappingRule, MappingSet};
|
||||
use crank_registry::{CreateAsyncJobRequest, CreateStreamSessionRequest, PostgresRegistry};
|
||||
@@ -1067,6 +1067,173 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn rejects_unsupported_protocol_for_community_operation_create() {
|
||||
let registry = test_registry().await;
|
||||
let storage_root = test_storage_root("community_protocol_reject");
|
||||
let base_url = spawn_admin_api(build_test_app(registry, storage_root)).await;
|
||||
let client = authorized_client(&base_url).await;
|
||||
|
||||
let response = client
|
||||
.post(format!("{base_url}/operations"))
|
||||
.json(&test_websocket_operation_payload(
|
||||
"wss://example.com/stream",
|
||||
"telemetry_ws",
|
||||
))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
let status = response.status();
|
||||
let body = response.json::<Value>().await.unwrap();
|
||||
|
||||
assert_eq!(status, reqwest::StatusCode::BAD_REQUEST);
|
||||
assert_eq!(body["error"]["code"], "validation_error");
|
||||
assert_eq!(
|
||||
body["error"]["message"],
|
||||
"protocol websocket is not supported in Community"
|
||||
);
|
||||
assert_eq!(
|
||||
body["error"]["context"],
|
||||
json!({
|
||||
"protocol": "websocket",
|
||||
"edition": "community",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn rejects_premium_security_level_for_community_operation_create() {
|
||||
let registry = test_registry().await;
|
||||
let storage_root = test_storage_root("community_security_reject_create");
|
||||
let upstream_base_url = spawn_upstream_server().await;
|
||||
let base_url = spawn_admin_api(build_test_app(registry, storage_root)).await;
|
||||
let client = authorized_client(&base_url).await;
|
||||
|
||||
let mut payload = test_operation_payload(&upstream_base_url, "crm_elevated");
|
||||
payload.security_level = OperationSecurityLevel::Elevated;
|
||||
|
||||
let response = client
|
||||
.post(format!("{base_url}/operations"))
|
||||
.json(&payload)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
let status = response.status();
|
||||
let body = response.json::<Value>().await.unwrap();
|
||||
|
||||
assert_eq!(status, reqwest::StatusCode::BAD_REQUEST);
|
||||
assert_eq!(body["error"]["code"], "validation_error");
|
||||
assert_eq!(
|
||||
body["error"]["message"],
|
||||
"security level elevated is not supported in Community"
|
||||
);
|
||||
assert_eq!(
|
||||
body["error"]["context"],
|
||||
json!({
|
||||
"security_level": "elevated",
|
||||
"edition": "community",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn rejects_premium_security_level_for_community_operation_update() {
|
||||
let registry = test_registry().await;
|
||||
let storage_root = test_storage_root("community_security_reject_update");
|
||||
let upstream_base_url = spawn_upstream_server().await;
|
||||
let base_url = spawn_admin_api(build_test_app(registry, storage_root)).await;
|
||||
let client = authorized_client(&base_url).await;
|
||||
|
||||
let created = client
|
||||
.post(format!("{base_url}/operations"))
|
||||
.json(&test_operation_payload(
|
||||
&upstream_base_url,
|
||||
"crm_update_elevated",
|
||||
))
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<Value>()
|
||||
.await
|
||||
.unwrap();
|
||||
let operation_id = created["operation_id"].as_str().unwrap().to_owned();
|
||||
|
||||
let response = client
|
||||
.patch(format!("{base_url}/operations/{operation_id}"))
|
||||
.json(&json!({
|
||||
"display_name": "Create Lead",
|
||||
"category": "sales",
|
||||
"security_level": "elevated",
|
||||
"target": {
|
||||
"kind": "rest",
|
||||
"base_url": upstream_base_url,
|
||||
"method": "POST",
|
||||
"path_template": "/crm/leads",
|
||||
"static_headers": {}
|
||||
},
|
||||
"input_schema": object_schema("email"),
|
||||
"output_schema": object_schema("id"),
|
||||
"input_mapping": {
|
||||
"rules": [{
|
||||
"source": "$.mcp.email",
|
||||
"target": "$.request.body.email",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"transform": null,
|
||||
"condition": null,
|
||||
"notes": null
|
||||
}]
|
||||
},
|
||||
"output_mapping": {
|
||||
"rules": [{
|
||||
"source": "$.response.body.id",
|
||||
"target": "$.output.id",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"transform": null,
|
||||
"condition": null,
|
||||
"notes": null
|
||||
}]
|
||||
},
|
||||
"execution_config": {
|
||||
"timeout_ms": 1000,
|
||||
"retry_policy": null,
|
||||
"auth_profile_ref": null,
|
||||
"headers": {},
|
||||
"protocol_options": null,
|
||||
"streaming": null
|
||||
},
|
||||
"tool_description": {
|
||||
"title": "Create Lead",
|
||||
"description": "Creates a CRM lead",
|
||||
"tags": ["crm"],
|
||||
"examples": []
|
||||
}
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
let status = response.status();
|
||||
let body = response.json::<Value>().await.unwrap();
|
||||
|
||||
assert_eq!(status, reqwest::StatusCode::BAD_REQUEST);
|
||||
assert_eq!(body["error"]["code"], "validation_error");
|
||||
assert_eq!(
|
||||
body["error"]["message"],
|
||||
"security level elevated is not supported in Community"
|
||||
);
|
||||
assert_eq!(
|
||||
body["error"]["context"],
|
||||
json!({
|
||||
"security_level": "elevated",
|
||||
"edition": "community",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn manages_workspace_access_lifecycle() {
|
||||
@@ -2375,6 +2542,63 @@ mod tests {
|
||||
assert_eq!(imported["import_mode"], "upsert");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn rejects_premium_security_level_for_community_yaml_upsert() {
|
||||
let registry = test_registry().await;
|
||||
let storage_root = test_storage_root("community_yaml_security_reject");
|
||||
let upstream_base_url = spawn_upstream_server().await;
|
||||
let base_url = spawn_admin_api(build_test_app(registry, storage_root)).await;
|
||||
let client = authorized_client(&base_url).await;
|
||||
|
||||
let created = client
|
||||
.post(format!("{base_url}/operations"))
|
||||
.json(&test_operation_payload(
|
||||
&upstream_base_url,
|
||||
"crm_yaml_standard",
|
||||
))
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<Value>()
|
||||
.await
|
||||
.unwrap();
|
||||
let operation_id = created["operation_id"].as_str().unwrap().to_owned();
|
||||
|
||||
let mut yaml = client
|
||||
.get(format!("{base_url}/operations/{operation_id}/export"))
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.text()
|
||||
.await
|
||||
.unwrap();
|
||||
yaml = yaml.replace("security_level: standard", "security_level: elevated");
|
||||
|
||||
let response = client
|
||||
.post(format!("{base_url}/operations/import?mode=upsert"))
|
||||
.body(yaml)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
let status = response.status();
|
||||
let body = response.json::<Value>().await.unwrap();
|
||||
|
||||
assert_eq!(status, reqwest::StatusCode::BAD_REQUEST);
|
||||
assert_eq!(body["error"]["code"], "validation_error");
|
||||
assert_eq!(
|
||||
body["error"]["message"],
|
||||
"security level elevated is not supported in Community"
|
||||
);
|
||||
assert_eq!(
|
||||
body["error"]["context"],
|
||||
json!({
|
||||
"security_level": "elevated",
|
||||
"edition": "community",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn manages_workspace_secrets_without_exposing_plaintext() {
|
||||
@@ -3069,6 +3293,7 @@ mod tests {
|
||||
display_name: "Create Lead".to_owned(),
|
||||
category: "sales".to_owned(),
|
||||
protocol: Protocol::Rest,
|
||||
security_level: OperationSecurityLevel::Standard,
|
||||
target: Target::Rest(RestTarget {
|
||||
base_url: base_url.to_owned(),
|
||||
method: HttpMethod::Post,
|
||||
@@ -3122,6 +3347,7 @@ mod tests {
|
||||
display_name: "Create Lead GraphQL".to_owned(),
|
||||
category: "sales".to_owned(),
|
||||
protocol: Protocol::Graphql,
|
||||
security_level: OperationSecurityLevel::Standard,
|
||||
target: Target::Graphql(GraphqlTarget {
|
||||
endpoint: endpoint.to_owned(),
|
||||
operation_type: GraphqlOperationType::Mutation,
|
||||
@@ -3178,6 +3404,7 @@ mod tests {
|
||||
display_name: "Unary Echo gRPC".to_owned(),
|
||||
category: "support".to_owned(),
|
||||
protocol: Protocol::Grpc,
|
||||
security_level: OperationSecurityLevel::Standard,
|
||||
target: Target::Grpc(GrpcTarget {
|
||||
server_addr: server_addr.to_owned(),
|
||||
package: "echo".to_owned(),
|
||||
@@ -3325,6 +3552,7 @@ mod tests {
|
||||
display_name: "Create Lead SOAP".to_owned(),
|
||||
category: "sales".to_owned(),
|
||||
protocol: Protocol::Soap,
|
||||
security_level: OperationSecurityLevel::Standard,
|
||||
target: Target::Soap(SoapTarget {
|
||||
wsdl_ref: "sample_wsdl".into(),
|
||||
service_name: "LeadService".to_owned(),
|
||||
@@ -3383,6 +3611,61 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn test_websocket_operation_payload(url: &str, name: &str) -> OperationPayload {
|
||||
OperationPayload {
|
||||
name: name.to_owned(),
|
||||
display_name: "Telemetry WebSocket".to_owned(),
|
||||
category: "telemetry".to_owned(),
|
||||
protocol: Protocol::Websocket,
|
||||
security_level: OperationSecurityLevel::Standard,
|
||||
target: Target::Websocket(WebsocketTarget {
|
||||
url: url.to_owned(),
|
||||
subprotocols: Vec::new(),
|
||||
subscribe_message_template: Some(json!({ "subscribe": true })),
|
||||
unsubscribe_message_template: None,
|
||||
static_headers: BTreeMap::new(),
|
||||
}),
|
||||
input_schema: object_schema("channel"),
|
||||
output_schema: object_schema("event"),
|
||||
input_mapping: MappingSet {
|
||||
rules: vec![MappingRule {
|
||||
source: "$.mcp.channel".to_owned(),
|
||||
target: "$.request.body.channel".to_owned(),
|
||||
required: true,
|
||||
default_value: None,
|
||||
transform: None,
|
||||
condition: None,
|
||||
notes: None,
|
||||
}],
|
||||
},
|
||||
output_mapping: MappingSet {
|
||||
rules: vec![MappingRule {
|
||||
source: "$.response.body.event".to_owned(),
|
||||
target: "$.output.event".to_owned(),
|
||||
required: true,
|
||||
default_value: None,
|
||||
transform: None,
|
||||
condition: None,
|
||||
notes: None,
|
||||
}],
|
||||
},
|
||||
execution_config: ExecutionConfig {
|
||||
timeout_ms: 1_000,
|
||||
retry_policy: None,
|
||||
auth_profile_ref: None,
|
||||
headers: BTreeMap::new(),
|
||||
protocol_options: None,
|
||||
streaming: None,
|
||||
},
|
||||
tool_description: ToolDescription {
|
||||
title: "Telemetry WebSocket".to_owned(),
|
||||
description: "Streams telemetry events".to_owned(),
|
||||
tags: vec!["telemetry".to_owned(), "websocket".to_owned()],
|
||||
examples: Vec::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const SOAP_TEST_WSDL: &str = r#"<?xml version="1.0"?>
|
||||
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
|
||||
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
|
||||
|
||||
@@ -101,6 +101,8 @@ pub struct OperationPayload {
|
||||
#[serde(default = "default_operation_category")]
|
||||
pub category: String,
|
||||
pub protocol: Protocol,
|
||||
#[serde(default)]
|
||||
pub security_level: OperationSecurityLevel,
|
||||
pub target: Target,
|
||||
pub input_schema: Schema,
|
||||
pub output_schema: Schema,
|
||||
@@ -570,6 +572,8 @@ pub struct UpdateOperationPayload {
|
||||
pub display_name: String,
|
||||
#[serde(default = "default_operation_category")]
|
||||
pub category: String,
|
||||
#[serde(default)]
|
||||
pub security_level: OperationSecurityLevel,
|
||||
pub target: Target,
|
||||
pub input_schema: Schema,
|
||||
pub output_schema: Schema,
|
||||
@@ -617,6 +621,7 @@ pub struct OperationSummaryView {
|
||||
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,
|
||||
@@ -637,6 +642,7 @@ pub struct OperationDetailView {
|
||||
pub display_name: String,
|
||||
pub category: String,
|
||||
pub protocol: Protocol,
|
||||
pub security_level: OperationSecurityLevel,
|
||||
pub status: OperationStatus,
|
||||
pub current_draft_version: u32,
|
||||
pub latest_published_version: Option<u32>,
|
||||
@@ -1966,6 +1972,7 @@ impl AdminService {
|
||||
display_name: summary.display_name,
|
||||
category: summary.category,
|
||||
protocol: summary.protocol,
|
||||
security_level: summary.security_level,
|
||||
status: summary.status,
|
||||
current_draft_version: summary.current_draft_version,
|
||||
latest_published_version: summary.latest_published_version,
|
||||
@@ -2036,6 +2043,7 @@ impl AdminService {
|
||||
display_name: payload.display_name,
|
||||
category: payload.category,
|
||||
protocol: payload.protocol,
|
||||
security_level: payload.security_level,
|
||||
status: OperationStatus::Draft,
|
||||
version: 1,
|
||||
target: payload.target,
|
||||
@@ -2097,6 +2105,7 @@ impl AdminService {
|
||||
display_name: payload.operation.display_name,
|
||||
category: payload.operation.category,
|
||||
protocol: payload.operation.protocol,
|
||||
security_level: payload.operation.security_level,
|
||||
status: OperationStatus::Draft,
|
||||
version,
|
||||
target: payload.operation.target,
|
||||
@@ -2160,6 +2169,7 @@ impl AdminService {
|
||||
display_name: payload.display_name,
|
||||
category: payload.category,
|
||||
protocol: existing.snapshot.protocol,
|
||||
security_level: payload.security_level,
|
||||
status: OperationStatus::Draft,
|
||||
version: existing.version,
|
||||
target: payload.target,
|
||||
@@ -3661,6 +3671,7 @@ impl AdminService {
|
||||
display_name: document.operation.display_name.clone(),
|
||||
category: document.operation.category.clone(),
|
||||
protocol: document.operation.protocol,
|
||||
security_level: document.operation.security_level,
|
||||
target: document.operation.target.clone(),
|
||||
input_schema: document.operation.input_schema.clone(),
|
||||
output_schema: document.operation.output_schema.clone(),
|
||||
@@ -3841,6 +3852,7 @@ impl AdminService {
|
||||
}
|
||||
|
||||
fn validate_operation_payload(&self, payload: &OperationPayload) -> Result<(), ApiError> {
|
||||
self.validate_operation_capabilities(payload.protocol, payload.security_level)?;
|
||||
validate_protocol_target(payload.protocol, &payload.target)?;
|
||||
payload.input_mapping.validate_paths()?;
|
||||
payload.output_mapping.validate_paths()?;
|
||||
@@ -3848,12 +3860,52 @@ impl AdminService {
|
||||
}
|
||||
|
||||
fn validate_registry_operation(&self, operation: &RegistryOperation) -> Result<(), ApiError> {
|
||||
self.validate_operation_capabilities(operation.protocol, operation.security_level)?;
|
||||
validate_protocol_target(operation.protocol, &operation.target)?;
|
||||
operation.input_mapping.validate_paths()?;
|
||||
operation.output_mapping.validate_paths()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_operation_capabilities(
|
||||
&self,
|
||||
protocol: Protocol,
|
||||
security_level: OperationSecurityLevel,
|
||||
) -> Result<(), ApiError> {
|
||||
let supported_protocols = [Protocol::Rest, Protocol::Graphql, Protocol::Grpc];
|
||||
if !supported_protocols.contains(&protocol) {
|
||||
return Err(ApiError::validation_with_context(
|
||||
format!(
|
||||
"protocol {} is not supported in Community",
|
||||
serde_json::to_string(&protocol)
|
||||
.unwrap_or_else(|_| "\"unknown\"".to_owned())
|
||||
.trim_matches('"')
|
||||
),
|
||||
json!({
|
||||
"protocol": protocol,
|
||||
"edition": "community",
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
if security_level != OperationSecurityLevel::Standard {
|
||||
return Err(ApiError::validation_with_context(
|
||||
format!(
|
||||
"security level {} is not supported in Community",
|
||||
serde_json::to_string(&security_level)
|
||||
.unwrap_or_else(|_| "\"unknown\"".to_owned())
|
||||
.trim_matches('"')
|
||||
),
|
||||
json!({
|
||||
"security_level": security_level,
|
||||
"edition": "community",
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn find_operation_by_name(
|
||||
&self,
|
||||
workspace_id: &WorkspaceId,
|
||||
@@ -4665,6 +4717,7 @@ fn demo_rest_operation_payload() -> OperationPayload {
|
||||
display_name: "Create CRM Lead".to_owned(),
|
||||
category: "sales".to_owned(),
|
||||
protocol: Protocol::Rest,
|
||||
security_level: OperationSecurityLevel::Standard,
|
||||
target: Target::Rest(crank_core::RestTarget {
|
||||
base_url: "https://crm.demo.internal".to_owned(),
|
||||
method: crank_core::HttpMethod::Post,
|
||||
@@ -4718,6 +4771,7 @@ fn demo_graphql_operation_payload() -> OperationPayload {
|
||||
display_name: "Get Invoice Status".to_owned(),
|
||||
category: "finance".to_owned(),
|
||||
protocol: Protocol::Graphql,
|
||||
security_level: OperationSecurityLevel::Standard,
|
||||
target: Target::Graphql(crank_core::GraphqlTarget {
|
||||
endpoint: "https://billing.demo.internal/graphql".to_owned(),
|
||||
operation_type: crank_core::GraphqlOperationType::Query,
|
||||
@@ -4769,6 +4823,7 @@ fn demo_grpc_operation_payload() -> OperationPayload {
|
||||
display_name: "Lookup Support Ticket".to_owned(),
|
||||
category: "support".to_owned(),
|
||||
protocol: Protocol::Grpc,
|
||||
security_level: OperationSecurityLevel::Standard,
|
||||
target: Target::Grpc(crank_core::GrpcTarget {
|
||||
server_addr: "http://grpc.demo.internal".to_owned(),
|
||||
package: "echo".to_owned(),
|
||||
@@ -4833,6 +4888,7 @@ fn demo_archived_operation_payload() -> OperationPayload {
|
||||
display_name: "Archive Marketing Contact".to_owned(),
|
||||
category: "marketing".to_owned(),
|
||||
protocol: Protocol::Rest,
|
||||
security_level: OperationSecurityLevel::Standard,
|
||||
target: Target::Rest(crank_core::RestTarget {
|
||||
base_url: "https://marketing.demo.internal".to_owned(),
|
||||
method: crank_core::HttpMethod::Patch,
|
||||
@@ -5341,6 +5397,7 @@ fn enrich_operation_summary(
|
||||
display_name: summary.display_name,
|
||||
category: summary.category,
|
||||
protocol: summary.protocol,
|
||||
security_level: summary.security_level,
|
||||
target_url: summary.target_url,
|
||||
target_action: summary.target_action,
|
||||
status: summary.status,
|
||||
|
||||
@@ -2778,6 +2778,7 @@ mod tests {
|
||||
display_name: "Create Lead".to_owned(),
|
||||
category: "sales".to_owned(),
|
||||
protocol: Protocol::Rest,
|
||||
security_level: crank_core::OperationSecurityLevel::Standard,
|
||||
status: OperationStatus::Published,
|
||||
version: 1,
|
||||
target: Target::Rest(RestTarget {
|
||||
@@ -2840,6 +2841,7 @@ mod tests {
|
||||
display_name: "Create Lead GraphQL".to_owned(),
|
||||
category: "sales".to_owned(),
|
||||
protocol: Protocol::Graphql,
|
||||
security_level: crank_core::OperationSecurityLevel::Standard,
|
||||
status: OperationStatus::Published,
|
||||
version: 1,
|
||||
target: Target::Graphql(GraphqlTarget {
|
||||
@@ -2907,6 +2909,7 @@ mod tests {
|
||||
display_name: "Unary Echo gRPC".to_owned(),
|
||||
category: "support".to_owned(),
|
||||
protocol: Protocol::Grpc,
|
||||
security_level: crank_core::OperationSecurityLevel::Standard,
|
||||
status: OperationStatus::Published,
|
||||
version: 1,
|
||||
target: Target::Grpc(GrpcTarget {
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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?;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -136,6 +136,13 @@
|
||||
- у операции задается обязательный `security_level`;
|
||||
- допустимые значения: `standard`, `elevated`, `strict`;
|
||||
- этот параметр определяет минимально допустимый режим машинного доступа при вызове через MCP.
|
||||
- если поле не передано, используется `standard` для обратной совместимости с уже существующим YAML и draft payload;
|
||||
- в `Community` backend принимает только:
|
||||
- `REST`
|
||||
- `GraphQL`
|
||||
- `gRPC`
|
||||
- `security_level = standard`
|
||||
- попытка создать, обновить или импортировать операцию с неподдерживаемым `protocol` или `security_level` должна завершаться `validation_error` еще на стороне `admin-api`, а не только скрываться в UI.
|
||||
|
||||
### 5.4. Samples and descriptors
|
||||
|
||||
|
||||
Reference in New Issue
Block a user