registry: add sqlx checks for agent and operation reads

This commit is contained in:
a.tolmachev
2026-04-12 21:07:30 +00:00
parent 6a909feb64
commit 934c872753
10 changed files with 1176 additions and 165 deletions
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
+55 -16
View File
@@ -5,7 +5,7 @@ impl PostgresRegistry {
&self,
workspace_id: &WorkspaceId,
) -> Result<Vec<AgentSummary>, 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<Option<AgentSummary>, 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(
+273 -123
View File
@@ -594,56 +594,6 @@ fn map_invocation_log_record(row: &PgRow) -> Result<InvocationLogRecord, Registr
})
}
fn map_agent_summary(row: &PgRow) -> Result<AgentSummary, RegistryError> {
Ok(AgentSummary {
id: AgentId::new(row.try_get::<String, _>("id")?),
workspace_id: WorkspaceId::new(row.try_get::<String, _>("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::<String, _>("status")?, "status")?,
current_draft_version: from_db_version(
row.try_get("current_draft_version")?,
"current_draft_version",
)?,
latest_published_version: row
.try_get::<Option<i32>, _>("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<OperationSummary, RegistryError> {
let target: Target = deserialize_json_value(row.try_get::<Json<Value>, _>("target_json")?.0)?;
let (target_url, target_action) = target_summary(&target);
Ok(OperationSummary {
id: OperationId::new(row.try_get::<String, _>("id")?),
workspace_id: WorkspaceId::new(row.try_get::<String, _>("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::<String, _>("protocol")?, "protocol")?,
target_url,
target_action,
status: deserialize_enum_text(&row.try_get::<String, _>("status")?, "status")?,
current_draft_version: from_db_version(
row.try_get("current_draft_version")?,
"current_draft_version",
)?,
latest_published_version: row
.try_get::<Option<i32>, _>("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<OperationUsageSummary, RegistryError> {
Ok(OperationUsageSummary {
operation_id: OperationId::new(row.try_get::<String, _>("operation_id")?),
@@ -716,62 +666,35 @@ fn map_operation_agent_ref(row: &PgRow) -> Result<OperationAgentRef, RegistryErr
}
fn map_operation_version_record(row: &PgRow) -> Result<OperationVersionRecord, RegistryError> {
let operation_id = OperationId::new(row.try_get::<String, _>("id")?);
let version = from_db_version(row.try_get("version")?, "version")?;
let status = deserialize_enum_text(&row.try_get::<String, _>("status")?, "status")?;
Ok(OperationVersionRecord {
operation_id: operation_id.clone(),
workspace_id: WorkspaceId::new(row.try_get::<String, _>("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::<String, _>("protocol")?, "protocol")?,
status,
version,
target: deserialize_json_value(row.try_get::<Json<Value>, _>("target_json")?.0)?,
input_schema: deserialize_json_value(
row.try_get::<Json<Value>, _>("input_schema_json")?.0,
)?,
output_schema: deserialize_json_value(
row.try_get::<Json<Value>, _>("output_schema_json")?.0,
)?,
input_mapping: deserialize_json_value(
row.try_get::<Json<Value>, _>("input_mapping_json")?.0,
)?,
output_mapping: deserialize_json_value(
row.try_get::<Json<Value>, _>("output_mapping_json")?.0,
)?,
execution_config: deserialize_json_value(
row.try_get::<Json<Value>, _>("execution_config_json")?.0,
)?,
tool_description: deserialize_json_value(
row.try_get::<Json<Value>, _>("tool_description_json")?.0,
)?,
samples: row
.try_get::<Option<Json<Value>>, _>("samples_json")?
.map(|value| deserialize_json_value(value.0))
.transpose()?,
generated_draft: row
.try_get::<Option<Json<Value>>, _>("generated_draft_json")?
.map(|value| deserialize_json_value(value.0))
.transpose()?,
config_export: row
.try_get::<Option<Json<Value>>, _>("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::<Json<Value>, _>("target_json")?.0,
row.try_get::<Json<Value>, _>("input_schema_json")?.0,
row.try_get::<Json<Value>, _>("output_schema_json")?.0,
row.try_get::<Json<Value>, _>("input_mapping_json")?.0,
row.try_get::<Json<Value>, _>("output_mapping_json")?.0,
row.try_get::<Json<Value>, _>("execution_config_json")?.0,
row.try_get::<Json<Value>, _>("tool_description_json")?.0,
row.try_get::<Option<Json<Value>>, _>("samples_json")?
.map(|value| value.0),
row.try_get::<Option<Json<Value>>, _>("generated_draft_json")?
.map(|value| value.0),
row.try_get::<Option<Json<Value>>, _>("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<AuthProfile, RegistryError> {
@@ -797,30 +720,168 @@ fn map_agent_binding(row: &PgRow) -> Result<AgentOperationBinding, RegistryError
})
}
fn map_agent_version_record(
#[allow(clippy::too_many_arguments)]
fn build_agent_summary(
id: String,
workspace_id: String,
slug: String,
display_name: String,
description: String,
status: String,
current_draft_version: i32,
latest_published_version: Option<i32>,
created_at: String,
updated_at: String,
published_at: Option<String>,
) -> Result<AgentSummary, RegistryError> {
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<i32>,
created_at: String,
updated_at: String,
published_at: Option<String>,
) -> Result<OperationSummary, RegistryError> {
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<AgentOperationBinding>,
) -> Result<AgentVersionRecord, RegistryError> {
let version = from_db_version(row.try_get("version")?, "version")?;
let status = deserialize_enum_text(&row.try_get::<String, _>("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::<Json<Value>, _>("instructions_json")?.0,
tool_selection_policy: row
.try_get::<Json<Value>, _>("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<String>,
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<Value>,
generated_draft_json: Option<Value>,
config_export_json: Option<Value>,
change_note: Option<String>,
created_at: String,
created_by: Option<String>,
) -> Result<OperationVersionRecord, RegistryError> {
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),
+118 -26
View File
@@ -308,7 +308,7 @@ impl PostgresRegistry {
&self,
workspace_id: &WorkspaceId,
) -> Result<Vec<OperationSummary>, 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<Option<OperationSummary>, 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<Option<OperationVersionRecord>, 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<Vec<OperationVersionRecord>, 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(