diff --git a/.sqlx/query-01dd17d87f2ad2d3c4cc10e5fb6430838a4556a1d783e452398cd15dfa45b293.json b/.sqlx/query-01dd17d87f2ad2d3c4cc10e5fb6430838a4556a1d783e452398cd15dfa45b293.json new file mode 100644 index 0000000..9398261 --- /dev/null +++ b/.sqlx/query-01dd17d87f2ad2d3c4cc10e5fb6430838a4556a1d783e452398cd15dfa45b293.json @@ -0,0 +1,161 @@ +{ + "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 to_char(o.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"operation_created_at!\",\n to_char(o.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"operation_updated_at!\",\n to_char(o.published_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as operation_published_at,\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 to_char(ov.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\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": [ + { + "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": "display_name", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "category", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "protocol", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "operation_created_at!", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "operation_updated_at!", + "type_info": "Text" + }, + { + "ordinal": 8, + "name": "operation_published_at", + "type_info": "Text" + }, + { + "ordinal": 9, + "name": "version", + "type_info": "Int4" + }, + { + "ordinal": 10, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 11, + "name": "target_json", + "type_info": "Jsonb" + }, + { + "ordinal": 12, + "name": "input_schema_json", + "type_info": "Jsonb" + }, + { + "ordinal": 13, + "name": "output_schema_json", + "type_info": "Jsonb" + }, + { + "ordinal": 14, + "name": "input_mapping_json", + "type_info": "Jsonb" + }, + { + "ordinal": 15, + "name": "output_mapping_json", + "type_info": "Jsonb" + }, + { + "ordinal": 16, + "name": "execution_config_json", + "type_info": "Jsonb" + }, + { + "ordinal": 17, + "name": "tool_description_json", + "type_info": "Jsonb" + }, + { + "ordinal": 18, + "name": "samples_json", + "type_info": "Jsonb" + }, + { + "ordinal": 19, + "name": "generated_draft_json", + "type_info": "Jsonb" + }, + { + "ordinal": 20, + "name": "config_export_json", + "type_info": "Jsonb" + }, + { + "ordinal": 21, + "name": "change_note", + "type_info": "Text" + }, + { + "ordinal": 22, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 23, + "name": "created_by", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + null, + null, + null, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + null, + true + ] + }, + "hash": "01dd17d87f2ad2d3c4cc10e5fb6430838a4556a1d783e452398cd15dfa45b293" +} diff --git a/.sqlx/query-81ac1c0cbe278e6e836853297075d320ea234e4cd3612dd0123711117017619d.json b/.sqlx/query-81ac1c0cbe278e6e836853297075d320ea234e4cd3612dd0123711117017619d.json new file mode 100644 index 0000000..ecaacfd --- /dev/null +++ b/.sqlx/query-81ac1c0cbe278e6e836853297075d320ea234e4cd3612dd0123711117017619d.json @@ -0,0 +1,83 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n id,\n workspace_id,\n slug,\n display_name,\n description,\n status,\n current_draft_version,\n latest_published_version,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\",\n to_char(published_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as published_at\n from agents\n where workspace_id = $1 and id = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "workspace_id", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "slug", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "display_name", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "description", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "current_draft_version", + "type_info": "Int4" + }, + { + "ordinal": 7, + "name": "latest_published_version", + "type_info": "Int4" + }, + { + "ordinal": 8, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 9, + "name": "updated_at!", + "type_info": "Text" + }, + { + "ordinal": 10, + "name": "published_at", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + true, + null, + null, + null + ] + }, + "hash": "81ac1c0cbe278e6e836853297075d320ea234e4cd3612dd0123711117017619d" +} diff --git a/.sqlx/query-ad2027435c7f2973f96a5b3d62afed74d6cf6c1ceb6f93c6fb7bcab5a6b7688f.json b/.sqlx/query-ad2027435c7f2973f96a5b3d62afed74d6cf6c1ceb6f93c6fb7bcab5a6b7688f.json new file mode 100644 index 0000000..a6cb9c1 --- /dev/null +++ b/.sqlx/query-ad2027435c7f2973f96a5b3d62afed74d6cf6c1ceb6f93c6fb7bcab5a6b7688f.json @@ -0,0 +1,53 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n agent_id,\n version,\n status,\n instructions_json,\n tool_selection_policy_json,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\"\n from agent_versions\n where agent_id = $1 and version = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "agent_id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "version", + "type_info": "Int4" + }, + { + "ordinal": 2, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "instructions_json", + "type_info": "Jsonb" + }, + { + "ordinal": 4, + "name": "tool_selection_policy_json", + "type_info": "Jsonb" + }, + { + "ordinal": 5, + "name": "created_at!", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Int4" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + null + ] + }, + "hash": "ad2027435c7f2973f96a5b3d62afed74d6cf6c1ceb6f93c6fb7bcab5a6b7688f" +} diff --git a/.sqlx/query-b1bc2d2ab01d17d8f06a4956457e6b0bb560a808b4b5635a1c38e1ce6892cfb8.json b/.sqlx/query-b1bc2d2ab01d17d8f06a4956457e6b0bb560a808b4b5635a1c38e1ce6892cfb8.json new file mode 100644 index 0000000..be8ea5a --- /dev/null +++ b/.sqlx/query-b1bc2d2ab01d17d8f06a4956457e6b0bb560a808b4b5635a1c38e1ce6892cfb8.json @@ -0,0 +1,82 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n id,\n workspace_id,\n slug,\n display_name,\n description,\n status,\n current_draft_version,\n latest_published_version,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\",\n to_char(published_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as published_at\n from agents\n where workspace_id = $1\n order by slug asc", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "workspace_id", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "slug", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "display_name", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "description", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "current_draft_version", + "type_info": "Int4" + }, + { + "ordinal": 7, + "name": "latest_published_version", + "type_info": "Int4" + }, + { + "ordinal": 8, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 9, + "name": "updated_at!", + "type_info": "Text" + }, + { + "ordinal": 10, + "name": "published_at", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + true, + null, + null, + null + ] + }, + "hash": "b1bc2d2ab01d17d8f06a4956457e6b0bb560a808b4b5635a1c38e1ce6892cfb8" +} diff --git a/.sqlx/query-d9beed1c482720fbb55525223107474328f347661607f15b248052149625bc78.json b/.sqlx/query-d9beed1c482720fbb55525223107474328f347661607f15b248052149625bc78.json new file mode 100644 index 0000000..2bc4aaf --- /dev/null +++ b/.sqlx/query-d9beed1c482720fbb55525223107474328f347661607f15b248052149625bc78.json @@ -0,0 +1,95 @@ +{ + "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 to_char(operations.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n to_char(operations.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\",\n to_char(operations.published_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as published_at\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": [ + { + "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": "display_name", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "category", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "protocol", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "target_json", + "type_info": "Jsonb" + }, + { + "ordinal": 7, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 8, + "name": "current_draft_version", + "type_info": "Int4" + }, + { + "ordinal": 9, + "name": "latest_published_version", + "type_info": "Int4" + }, + { + "ordinal": 10, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 11, + "name": "updated_at!", + "type_info": "Text" + }, + { + "ordinal": 12, + "name": "published_at", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + null, + null, + null + ] + }, + "hash": "d9beed1c482720fbb55525223107474328f347661607f15b248052149625bc78" +} diff --git a/.sqlx/query-dcd602287d6c524b98068b6e95936700b7bfa54d5e5f3d425f4b770193601bdc.json b/.sqlx/query-dcd602287d6c524b98068b6e95936700b7bfa54d5e5f3d425f4b770193601bdc.json new file mode 100644 index 0000000..96ab5dc --- /dev/null +++ b/.sqlx/query-dcd602287d6c524b98068b6e95936700b7bfa54d5e5f3d425f4b770193601bdc.json @@ -0,0 +1,94 @@ +{ + "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 to_char(operations.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n to_char(operations.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\",\n to_char(operations.published_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as published_at\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": [ + { + "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": "display_name", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "category", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "protocol", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "target_json", + "type_info": "Jsonb" + }, + { + "ordinal": 7, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 8, + "name": "current_draft_version", + "type_info": "Int4" + }, + { + "ordinal": 9, + "name": "latest_published_version", + "type_info": "Int4" + }, + { + "ordinal": 10, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 11, + "name": "updated_at!", + "type_info": "Text" + }, + { + "ordinal": 12, + "name": "published_at", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + null, + null, + null + ] + }, + "hash": "dcd602287d6c524b98068b6e95936700b7bfa54d5e5f3d425f4b770193601bdc" +} diff --git a/.sqlx/query-e093ff52fbbfe4039edec5ea5e5b6ecab4400b9a66ba5ef7408b0797d8b286cf.json b/.sqlx/query-e093ff52fbbfe4039edec5ea5e5b6ecab4400b9a66ba5ef7408b0797d8b286cf.json new file mode 100644 index 0000000..33b8b9f --- /dev/null +++ b/.sqlx/query-e093ff52fbbfe4039edec5ea5e5b6ecab4400b9a66ba5ef7408b0797d8b286cf.json @@ -0,0 +1,162 @@ +{ + "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 to_char(o.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"operation_created_at!\",\n to_char(o.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"operation_updated_at!\",\n to_char(o.published_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as operation_published_at,\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 to_char(ov.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\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": [ + { + "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": "display_name", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "category", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "protocol", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "operation_created_at!", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "operation_updated_at!", + "type_info": "Text" + }, + { + "ordinal": 8, + "name": "operation_published_at", + "type_info": "Text" + }, + { + "ordinal": 9, + "name": "version", + "type_info": "Int4" + }, + { + "ordinal": 10, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 11, + "name": "target_json", + "type_info": "Jsonb" + }, + { + "ordinal": 12, + "name": "input_schema_json", + "type_info": "Jsonb" + }, + { + "ordinal": 13, + "name": "output_schema_json", + "type_info": "Jsonb" + }, + { + "ordinal": 14, + "name": "input_mapping_json", + "type_info": "Jsonb" + }, + { + "ordinal": 15, + "name": "output_mapping_json", + "type_info": "Jsonb" + }, + { + "ordinal": 16, + "name": "execution_config_json", + "type_info": "Jsonb" + }, + { + "ordinal": 17, + "name": "tool_description_json", + "type_info": "Jsonb" + }, + { + "ordinal": 18, + "name": "samples_json", + "type_info": "Jsonb" + }, + { + "ordinal": 19, + "name": "generated_draft_json", + "type_info": "Jsonb" + }, + { + "ordinal": 20, + "name": "config_export_json", + "type_info": "Jsonb" + }, + { + "ordinal": 21, + "name": "change_note", + "type_info": "Text" + }, + { + "ordinal": 22, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 23, + "name": "created_by", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Int4" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + null, + null, + null, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + null, + true + ] + }, + "hash": "e093ff52fbbfe4039edec5ea5e5b6ecab4400b9a66ba5ef7408b0797d8b286cf" +} diff --git a/crates/crank-registry/src/postgres/agent.rs b/crates/crank-registry/src/postgres/agent.rs index 4887166..15c78af 100644 --- a/crates/crank-registry/src/postgres/agent.rs +++ b/crates/crank-registry/src/postgres/agent.rs @@ -5,7 +5,7 @@ impl PostgresRegistry { &self, workspace_id: &WorkspaceId, ) -> Result, RegistryError> { - let rows = sqlx::query( + let rows = sqlx::query!( "select id, workspace_id, @@ -15,18 +15,34 @@ impl PostgresRegistry { status, current_draft_version, latest_published_version, - to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, - to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as updated_at, + to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", + to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\", to_char(published_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as published_at from agents where workspace_id = $1 order by slug asc", + workspace_id.as_str(), ) - .bind(workspace_id.as_str()) .fetch_all(&self.pool) .await?; - rows.iter().map(map_agent_summary).collect() + rows.into_iter() + .map(|row| { + build_agent_summary( + row.id, + row.workspace_id, + row.slug, + row.display_name, + row.description, + row.status, + row.current_draft_version, + row.latest_published_version, + row.created_at, + row.updated_at, + row.published_at, + ) + }) + .collect() } pub async fn get_agent_summary( @@ -34,7 +50,7 @@ impl PostgresRegistry { workspace_id: &WorkspaceId, agent_id: &AgentId, ) -> Result, RegistryError> { - let row = sqlx::query( + let row = sqlx::query!( "select id, workspace_id, @@ -44,18 +60,33 @@ impl PostgresRegistry { status, current_draft_version, latest_published_version, - to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, - to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as updated_at, + to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", + to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\", to_char(published_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as published_at from agents where workspace_id = $1 and id = $2", + workspace_id.as_str(), + agent_id.as_str(), ) - .bind(workspace_id.as_str()) - .bind(agent_id.as_str()) .fetch_optional(&self.pool) .await?; - row.as_ref().map(map_agent_summary).transpose() + row.map(|row| { + build_agent_summary( + row.id, + row.workspace_id, + row.slug, + row.display_name, + row.description, + row.status, + row.current_draft_version, + row.latest_published_version, + row.created_at, + row.updated_at, + row.published_at, + ) + }) + .transpose() } pub async fn create_agent(&self, request: CreateAgentRequest<'_>) -> Result<(), RegistryError> { @@ -116,19 +147,19 @@ impl PostgresRegistry { None => return Ok(None), }; - let row = sqlx::query( + let row = sqlx::query!( "select agent_id, version, status, instructions_json, tool_selection_policy_json, - to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at + to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\" from agent_versions where agent_id = $1 and version = $2", + agent_id.as_str(), + to_db_version(version), ) - .bind(agent_id.as_str()) - .bind(to_db_version(version)) .fetch_optional(&self.pool) .await?; @@ -137,7 +168,15 @@ impl PostgresRegistry { }; let bindings = self.list_agent_bindings(agent_id, version).await?; - Ok(Some(map_agent_version_record(&summary, &row, bindings)?)) + Ok(Some(build_agent_version_record( + &summary, + row.version, + row.status, + row.instructions_json, + row.tool_selection_policy_json, + row.created_at, + bindings, + )?)) } pub async fn save_agent_bindings( diff --git a/crates/crank-registry/src/postgres/mod.rs b/crates/crank-registry/src/postgres/mod.rs index ef695bf..de80330 100644 --- a/crates/crank-registry/src/postgres/mod.rs +++ b/crates/crank-registry/src/postgres/mod.rs @@ -594,56 +594,6 @@ fn map_invocation_log_record(row: &PgRow) -> Result Result { - Ok(AgentSummary { - id: AgentId::new(row.try_get::("id")?), - workspace_id: WorkspaceId::new(row.try_get::("workspace_id")?), - slug: row.try_get("slug")?, - display_name: row.try_get("display_name")?, - description: row.try_get("description")?, - status: deserialize_enum_text(&row.try_get::("status")?, "status")?, - current_draft_version: from_db_version( - row.try_get("current_draft_version")?, - "current_draft_version", - )?, - latest_published_version: row - .try_get::, _>("latest_published_version")? - .map(|value| from_db_version(value, "latest_published_version")) - .transpose()?, - created_at: row.try_get("created_at")?, - updated_at: row.try_get("updated_at")?, - published_at: row.try_get("published_at")?, - }) -} - -fn map_operation_summary(row: &PgRow) -> Result { - let target: Target = deserialize_json_value(row.try_get::, _>("target_json")?.0)?; - let (target_url, target_action) = target_summary(&target); - - Ok(OperationSummary { - id: OperationId::new(row.try_get::("id")?), - workspace_id: WorkspaceId::new(row.try_get::("workspace_id")?), - name: row.try_get("name")?, - display_name: row.try_get("display_name")?, - category: row.try_get("category")?, - protocol: deserialize_enum_text(&row.try_get::("protocol")?, "protocol")?, - target_url, - target_action, - status: deserialize_enum_text(&row.try_get::("status")?, "status")?, - current_draft_version: from_db_version( - row.try_get("current_draft_version")?, - "current_draft_version", - )?, - latest_published_version: row - .try_get::, _>("latest_published_version")? - .map(|value| from_db_version(value, "latest_published_version")) - .transpose()?, - created_at: row.try_get("created_at")?, - updated_at: row.try_get("updated_at")?, - published_at: row.try_get("published_at")?, - }) -} - fn map_operation_usage_summary(row: &PgRow) -> Result { Ok(OperationUsageSummary { operation_id: OperationId::new(row.try_get::("operation_id")?), @@ -716,62 +666,35 @@ fn map_operation_agent_ref(row: &PgRow) -> Result Result { - let operation_id = OperationId::new(row.try_get::("id")?); - let version = from_db_version(row.try_get("version")?, "version")?; - let status = deserialize_enum_text(&row.try_get::("status")?, "status")?; - - Ok(OperationVersionRecord { - operation_id: operation_id.clone(), - workspace_id: WorkspaceId::new(row.try_get::("workspace_id")?), - version, - status, - change_note: row.try_get("change_note")?, - created_at: row.try_get("created_at")?, - created_by: row.try_get("created_by")?, - snapshot: RegistryOperation { - id: operation_id, - name: row.try_get("name")?, - display_name: row.try_get("display_name")?, - category: row.try_get("category")?, - protocol: deserialize_enum_text(&row.try_get::("protocol")?, "protocol")?, - status, - version, - target: deserialize_json_value(row.try_get::, _>("target_json")?.0)?, - input_schema: deserialize_json_value( - row.try_get::, _>("input_schema_json")?.0, - )?, - output_schema: deserialize_json_value( - row.try_get::, _>("output_schema_json")?.0, - )?, - input_mapping: deserialize_json_value( - row.try_get::, _>("input_mapping_json")?.0, - )?, - output_mapping: deserialize_json_value( - row.try_get::, _>("output_mapping_json")?.0, - )?, - execution_config: deserialize_json_value( - row.try_get::, _>("execution_config_json")?.0, - )?, - tool_description: deserialize_json_value( - row.try_get::, _>("tool_description_json")?.0, - )?, - samples: row - .try_get::>, _>("samples_json")? - .map(|value| deserialize_json_value(value.0)) - .transpose()?, - generated_draft: row - .try_get::>, _>("generated_draft_json")? - .map(|value| deserialize_json_value(value.0)) - .transpose()?, - config_export: row - .try_get::>, _>("config_export_json")? - .map(|value| deserialize_json_value(value.0)) - .transpose()?, - created_at: row.try_get("operation_created_at")?, - updated_at: row.try_get("operation_updated_at")?, - published_at: row.try_get("operation_published_at")?, - }, - }) + build_operation_version_record( + row.try_get("id")?, + row.try_get("workspace_id")?, + row.try_get("name")?, + row.try_get("display_name")?, + row.try_get("category")?, + row.try_get("protocol")?, + row.try_get("operation_created_at")?, + row.try_get("operation_updated_at")?, + row.try_get("operation_published_at")?, + row.try_get("version")?, + row.try_get("status")?, + row.try_get::, _>("target_json")?.0, + row.try_get::, _>("input_schema_json")?.0, + row.try_get::, _>("output_schema_json")?.0, + row.try_get::, _>("input_mapping_json")?.0, + row.try_get::, _>("output_mapping_json")?.0, + row.try_get::, _>("execution_config_json")?.0, + row.try_get::, _>("tool_description_json")?.0, + row.try_get::>, _>("samples_json")? + .map(|value| value.0), + row.try_get::>, _>("generated_draft_json")? + .map(|value| value.0), + row.try_get::>, _>("config_export_json")? + .map(|value| value.0), + row.try_get("change_note")?, + row.try_get("created_at")?, + row.try_get("created_by")?, + ) } fn map_auth_profile(row: &PgRow) -> Result { @@ -797,30 +720,168 @@ fn map_agent_binding(row: &PgRow) -> Result, + created_at: String, + updated_at: String, + published_at: Option, +) -> Result { + Ok(AgentSummary { + id: AgentId::new(id), + workspace_id: WorkspaceId::new(workspace_id), + slug, + display_name, + description, + status: deserialize_enum_text(&status, "status")?, + current_draft_version: from_db_version(current_draft_version, "current_draft_version")?, + latest_published_version: latest_published_version + .map(|value| from_db_version(value, "latest_published_version")) + .transpose()?, + created_at, + updated_at, + published_at, + }) +} + +#[allow(clippy::too_many_arguments)] +fn build_operation_summary( + id: String, + workspace_id: String, + name: String, + display_name: String, + category: String, + protocol: String, + target_json: Value, + status: String, + current_draft_version: i32, + latest_published_version: Option, + created_at: String, + updated_at: String, + published_at: Option, +) -> Result { + let target: Target = deserialize_json_value(target_json)?; + let (target_url, target_action) = target_summary(&target); + + Ok(OperationSummary { + id: OperationId::new(id), + workspace_id: WorkspaceId::new(workspace_id), + name, + display_name, + category, + protocol: deserialize_enum_text(&protocol, "protocol")?, + target_url, + target_action, + status: deserialize_enum_text(&status, "status")?, + current_draft_version: from_db_version(current_draft_version, "current_draft_version")?, + latest_published_version: latest_published_version + .map(|value| from_db_version(value, "latest_published_version")) + .transpose()?, + created_at, + updated_at, + published_at, + }) +} + +fn build_agent_version_record( summary: &AgentSummary, - row: &PgRow, + version: i32, + status: String, + instructions_json: Value, + tool_selection_policy_json: Value, + created_at: String, bindings: Vec, ) -> Result { - let version = from_db_version(row.try_get("version")?, "version")?; - let status = deserialize_enum_text(&row.try_get::("status")?, "status")?; + let version = from_db_version(version, "version")?; + let status = deserialize_enum_text(&status, "status")?; Ok(AgentVersionRecord { agent_id: summary.id.clone(), workspace_id: summary.workspace_id.clone(), version, status, - created_at: row.try_get("created_at")?, + created_at: created_at.clone(), bindings, snapshot: AgentVersion { agent_id: summary.id.clone(), version, status, - instructions: row.try_get::, _>("instructions_json")?.0, - tool_selection_policy: row - .try_get::, _>("tool_selection_policy_json")? - .0, - created_at: row.try_get("created_at")?, + instructions: instructions_json, + tool_selection_policy: tool_selection_policy_json, + created_at, + }, + }) +} + +#[allow(clippy::too_many_arguments)] +fn build_operation_version_record( + id: String, + workspace_id: String, + name: String, + display_name: String, + category: String, + protocol: String, + operation_created_at: String, + operation_updated_at: String, + operation_published_at: Option, + version: i32, + status: String, + target_json: Value, + input_schema_json: Value, + output_schema_json: Value, + input_mapping_json: Value, + output_mapping_json: Value, + execution_config_json: Value, + tool_description_json: Value, + samples_json: Option, + generated_draft_json: Option, + config_export_json: Option, + change_note: Option, + created_at: String, + created_by: Option, +) -> Result { + let operation_id = OperationId::new(id); + let version = from_db_version(version, "version")?; + let status = deserialize_enum_text(&status, "status")?; + + Ok(OperationVersionRecord { + operation_id: operation_id.clone(), + workspace_id: WorkspaceId::new(workspace_id), + version, + status, + change_note, + created_at: created_at.clone(), + created_by, + snapshot: RegistryOperation { + id: operation_id, + name, + display_name, + category, + protocol: deserialize_enum_text(&protocol, "protocol")?, + status, + version, + target: deserialize_json_value(target_json)?, + input_schema: deserialize_json_value(input_schema_json)?, + output_schema: deserialize_json_value(output_schema_json)?, + input_mapping: deserialize_json_value(input_mapping_json)?, + output_mapping: deserialize_json_value(output_mapping_json)?, + execution_config: deserialize_json_value(execution_config_json)?, + tool_description: deserialize_json_value(tool_description_json)?, + samples: samples_json.map(deserialize_json_value).transpose()?, + generated_draft: generated_draft_json + .map(deserialize_json_value) + .transpose()?, + config_export: config_export_json.map(deserialize_json_value).transpose()?, + created_at: operation_created_at, + updated_at: operation_updated_at, + published_at: operation_published_at, }, }) } @@ -1046,12 +1107,13 @@ mod tests { }; use crank_core::{ - ApiKeyHeaderAuthConfig, AsyncJobHandle, AuthConfig, AuthKind, AuthProfile, ConfigExport, - ExecutionConfig, ExportMode, GeneratedDraft, GeneratedDraftStatus, HttpMethod, JobStatus, - MembershipRole, OperationId, OperationStatus, PlatformApiKey, PlatformApiKeyId, - PlatformApiKeyScope, PlatformApiKeyStatus, Protocol, RestTarget, RetryPolicy, Samples, - SecretId, StreamSession, StreamSessionId, StreamStatus, Target, ToolDescription, - ToolExample, User, UserId, Workspace, WorkspaceId, + AgentId, AgentOperationBinding, AgentStatus, AgentVersion, ApiKeyHeaderAuthConfig, + AsyncJobHandle, AuthConfig, AuthKind, AuthProfile, ConfigExport, ExecutionConfig, + ExportMode, GeneratedDraft, GeneratedDraftStatus, HttpMethod, JobStatus, MembershipRole, + OperationId, OperationStatus, PlatformApiKey, PlatformApiKeyId, PlatformApiKeyScope, + PlatformApiKeyStatus, Protocol, RestTarget, RetryPolicy, Samples, SecretId, StreamSession, + StreamSessionId, StreamStatus, Target, ToolDescription, ToolExample, User, UserId, + Workspace, WorkspaceId, }; use crank_mapping::{MappingRule, MappingSet}; use crank_schema::{Schema, SchemaKind}; @@ -1061,7 +1123,7 @@ mod tests { use crate::{ PostgresRegistry, RegistryError, model::{ - AsyncJobFilter, CreateAsyncJobRequest, CreatePlatformApiKeyRequest, + AsyncJobFilter, CreateAgentRequest, CreateAsyncJobRequest, CreatePlatformApiKeyRequest, CreateStreamSessionRequest, CreateVersionRequest, CreateWorkspaceRequest, CreateYamlImportJobRequest, DescriptorKind, DescriptorMetadata, OperationSampleMetadata, PlatformApiKeyRecord, PublishRequest, RegistryOperation, @@ -1524,6 +1586,61 @@ mod tests { database.cleanup().await; } + #[tokio::test] + async fn manages_agent_read_paths() { + let database = TestDatabase::new().await; + let registry = database.registry().await; + let operation = test_operation("op_agent_01", 1, OperationStatus::Draft); + let agent = test_agent("agent_01", AgentStatus::Draft); + let version = test_agent_version(&agent.id, 1, AgentStatus::Draft); + let bindings = vec![AgentOperationBinding { + operation_id: operation.id.clone(), + operation_version: operation.version, + tool_name: "create_lead".to_owned(), + tool_title: "Create lead".to_owned(), + tool_description_override: Some("Creates CRM lead".to_owned()), + enabled: true, + }]; + + registry + .create_operation(&test_workspace_id(), &operation, None) + .await + .unwrap(); + registry + .create_agent(CreateAgentRequest { + agent: &agent, + version: &version, + bindings: &bindings, + }) + .await + .unwrap(); + + let listed = registry.list_agents(&test_workspace_id()).await.unwrap(); + let summary = registry + .get_agent_summary(&test_workspace_id(), &agent.id) + .await + .unwrap() + .unwrap(); + let loaded_version = registry + .get_agent_version(&test_workspace_id(), &agent.id, version.version) + .await + .unwrap() + .unwrap(); + + assert_eq!(listed, vec![summary.clone()]); + assert_eq!(summary.id, agent.id); + assert_eq!(summary.slug, agent.slug); + assert_eq!(summary.display_name, agent.display_name); + assert_eq!(summary.status, agent.status); + assert_eq!(loaded_version.agent_id, agent.id); + assert_eq!(loaded_version.version, version.version); + assert_eq!(loaded_version.status, version.status); + assert_eq!(loaded_version.snapshot, version); + assert_eq!(loaded_version.bindings, bindings); + + database.cleanup().await; + } + #[tokio::test] async fn manages_stream_sessions_with_transitions_and_cleanup() { let database = TestDatabase::new().await; @@ -1981,6 +2098,39 @@ mod tests { } } + fn test_agent(id: &str, status: AgentStatus) -> crank_core::Agent { + crank_core::Agent { + id: AgentId::new(id), + workspace_id: test_workspace_id(), + slug: format!("{id}_slug"), + display_name: format!("Display {id}"), + description: format!("Description {id}"), + status, + current_draft_version: 1, + latest_published_version: None, + created_at: "2026-03-25T11:58:00Z".to_owned(), + updated_at: "2026-03-25T12:00:00Z".to_owned(), + published_at: None, + } + } + + fn test_agent_version(agent_id: &AgentId, version: u32, status: AgentStatus) -> AgentVersion { + AgentVersion { + agent_id: agent_id.clone(), + version, + status, + instructions: json!({ + "system": "triage tickets", + "guardrails": ["don't mutate state"] + }), + tool_selection_policy: json!({ + "mode": "allow_list", + "max_tools": 8 + }), + created_at: "2026-03-25T12:00:00Z".to_owned(), + } + } + fn test_async_job(id: &str, operation_id: &str, status: JobStatus) -> AsyncJobHandle { AsyncJobHandle { id: crank_core::AsyncJobId::new(id), diff --git a/crates/crank-registry/src/postgres/operation.rs b/crates/crank-registry/src/postgres/operation.rs index d1d0fac..0d2433b 100644 --- a/crates/crank-registry/src/postgres/operation.rs +++ b/crates/crank-registry/src/postgres/operation.rs @@ -308,7 +308,7 @@ impl PostgresRegistry { &self, workspace_id: &WorkspaceId, ) -> Result, RegistryError> { - let rows = sqlx::query( + let rows = sqlx::query!( "select operations.id, operations.workspace_id, @@ -320,8 +320,8 @@ impl PostgresRegistry { operations.status, operations.current_draft_version, operations.latest_published_version, - to_char(operations.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, - to_char(operations.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as updated_at, + to_char(operations.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", + to_char(operations.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\", to_char(operations.published_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as published_at from operations join operation_versions ov @@ -329,12 +329,30 @@ impl PostgresRegistry { and ov.version = operations.current_draft_version where operations.workspace_id = $1 order by operations.name asc", + workspace_id.as_str(), ) - .bind(workspace_id.as_str()) .fetch_all(&self.pool) .await?; - rows.iter().map(map_operation_summary).collect() + rows.into_iter() + .map(|row| { + build_operation_summary( + row.id, + row.workspace_id, + row.name, + row.display_name, + row.category, + row.protocol, + row.target_json, + row.status, + row.current_draft_version, + row.latest_published_version, + row.created_at, + row.updated_at, + row.published_at, + ) + }) + .collect() } pub async fn list_operation_usage_summaries( @@ -393,7 +411,7 @@ impl PostgresRegistry { workspace_id: &WorkspaceId, operation_id: &OperationId, ) -> Result, RegistryError> { - let row = sqlx::query( + let row = sqlx::query!( "select operations.id, operations.workspace_id, @@ -405,21 +423,38 @@ impl PostgresRegistry { operations.status, operations.current_draft_version, operations.latest_published_version, - to_char(operations.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, - to_char(operations.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as updated_at, + to_char(operations.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", + to_char(operations.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\", to_char(operations.published_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as published_at from operations join operation_versions ov on ov.operation_id = operations.id and ov.version = operations.current_draft_version where operations.workspace_id = $1 and operations.id = $2", + workspace_id.as_str(), + operation_id.as_str(), ) - .bind(workspace_id.as_str()) - .bind(operation_id.as_str()) .fetch_optional(&self.pool) .await?; - row.as_ref().map(map_operation_summary).transpose() + row.map(|row| { + build_operation_summary( + row.id, + row.workspace_id, + row.name, + row.display_name, + row.category, + row.protocol, + row.target_json, + row.status, + row.current_draft_version, + row.latest_published_version, + row.created_at, + row.updated_at, + row.published_at, + ) + }) + .transpose() } pub async fn get_operation_version( @@ -428,7 +463,7 @@ impl PostgresRegistry { operation_id: &OperationId, version: u32, ) -> Result, RegistryError> { - let row = sqlx::query( + let row = sqlx::query!( "select o.id, o.workspace_id, @@ -436,8 +471,8 @@ impl PostgresRegistry { o.display_name, o.category, o.protocol, - to_char(o.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as operation_created_at, - to_char(o.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as operation_updated_at, + to_char(o.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"operation_created_at!\", + to_char(o.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"operation_updated_at!\", to_char(o.published_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as operation_published_at, ov.version, ov.status, @@ -452,19 +487,47 @@ impl PostgresRegistry { ov.generated_draft_json, ov.config_export_json, ov.change_note, - to_char(ov.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, + to_char(ov.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", ov.created_by from operation_versions ov join operations o on o.id = ov.operation_id where o.workspace_id = $1 and ov.operation_id = $2 and ov.version = $3", + workspace_id.as_str(), + operation_id.as_str(), + to_db_version(version), ) - .bind(workspace_id.as_str()) - .bind(operation_id.as_str()) - .bind(to_db_version(version)) .fetch_optional(&self.pool) .await?; - row.as_ref().map(map_operation_version_record).transpose() + row.map(|row| { + build_operation_version_record( + row.id, + row.workspace_id, + row.name, + row.display_name, + row.category, + row.protocol, + row.operation_created_at, + row.operation_updated_at, + row.operation_published_at, + row.version, + row.status, + row.target_json, + row.input_schema_json, + row.output_schema_json, + row.input_mapping_json, + row.output_mapping_json, + row.execution_config_json, + row.tool_description_json, + row.samples_json, + row.generated_draft_json, + row.config_export_json, + row.change_note, + row.created_at, + row.created_by, + ) + }) + .transpose() } pub async fn list_operation_versions( @@ -472,7 +535,7 @@ impl PostgresRegistry { workspace_id: &WorkspaceId, operation_id: &OperationId, ) -> Result, RegistryError> { - let rows = sqlx::query( + let rows = sqlx::query!( "select o.id, o.workspace_id, @@ -480,8 +543,8 @@ impl PostgresRegistry { o.display_name, o.category, o.protocol, - to_char(o.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as operation_created_at, - to_char(o.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as operation_updated_at, + to_char(o.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"operation_created_at!\", + to_char(o.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"operation_updated_at!\", to_char(o.published_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as operation_published_at, ov.version, ov.status, @@ -496,19 +559,48 @@ impl PostgresRegistry { ov.generated_draft_json, ov.config_export_json, ov.change_note, - to_char(ov.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, + to_char(ov.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", ov.created_by from operation_versions ov join operations o on o.id = ov.operation_id where o.workspace_id = $1 and ov.operation_id = $2 order by ov.version asc", + workspace_id.as_str(), + operation_id.as_str(), ) - .bind(workspace_id.as_str()) - .bind(operation_id.as_str()) .fetch_all(&self.pool) .await?; - rows.iter().map(map_operation_version_record).collect() + rows.into_iter() + .map(|row| { + build_operation_version_record( + row.id, + row.workspace_id, + row.name, + row.display_name, + row.category, + row.protocol, + row.operation_created_at, + row.operation_updated_at, + row.operation_published_at, + row.version, + row.status, + row.target_json, + row.input_schema_json, + row.output_schema_json, + row.input_mapping_json, + row.output_mapping_json, + row.execution_config_json, + row.tool_description_json, + row.samples_json, + row.generated_draft_json, + row.config_export_json, + row.change_note, + row.created_at, + row.created_by, + ) + }) + .collect() } pub async fn publish_operation(