core: type agent timestamps

This commit is contained in:
a.tolmachev
2026-04-19 07:11:55 +00:00
parent dddbbac745
commit 8d1f5284ba
9 changed files with 134 additions and 79 deletions
@@ -1,6 +1,6 @@
{
"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",
"query": "select\n agent_id,\n version,\n status,\n instructions_json,\n tool_selection_policy_json,\n created_at as \"created_at!: time::OffsetDateTime\"\n from agent_versions\n where agent_id = $1 and version = $2",
"describe": {
"columns": [
{
@@ -30,8 +30,8 @@
},
{
"ordinal": 5,
"name": "created_at!",
"type_info": "Text"
"name": "created_at!: time::OffsetDateTime",
"type_info": "Timestamptz"
}
],
"parameters": {
@@ -46,8 +46,8 @@
false,
false,
false,
null
false
]
},
"hash": "ad2027435c7f2973f96a5b3d62afed74d6cf6c1ceb6f93c6fb7bcab5a6b7688f"
"hash": "1912ca7a4b00fcc5c1a7fe5434b811977fbdf181436573fa96415ccfcb5fecb2"
}
@@ -1,6 +1,6 @@
{
"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",
"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 created_at as \"created_at!: time::OffsetDateTime\",\n updated_at as \"updated_at!: time::OffsetDateTime\",\n published_at as \"published_at: time::OffsetDateTime\"\n from agents\n where workspace_id = $1 and id = $2",
"describe": {
"columns": [
{
@@ -45,18 +45,18 @@
},
{
"ordinal": 8,
"name": "created_at!",
"type_info": "Text"
"name": "created_at!: time::OffsetDateTime",
"type_info": "Timestamptz"
},
{
"ordinal": 9,
"name": "updated_at!",
"type_info": "Text"
"name": "updated_at!: time::OffsetDateTime",
"type_info": "Timestamptz"
},
{
"ordinal": 10,
"name": "published_at",
"type_info": "Text"
"name": "published_at: time::OffsetDateTime",
"type_info": "Timestamptz"
}
],
"parameters": {
@@ -74,10 +74,10 @@
false,
false,
true,
null,
null,
null
false,
false,
true
]
},
"hash": "81ac1c0cbe278e6e836853297075d320ea234e4cd3612dd0123711117017619d"
"hash": "20a3404581cb52fa7087ba4649fde4910e8d7c2b1b17055fdd1c54206e55a874"
}
@@ -1,6 +1,6 @@
{
"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",
"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 created_at as \"created_at!: time::OffsetDateTime\",\n updated_at as \"updated_at!: time::OffsetDateTime\",\n published_at as \"published_at: time::OffsetDateTime\"\n from agents\n where workspace_id = $1\n order by slug asc",
"describe": {
"columns": [
{
@@ -45,18 +45,18 @@
},
{
"ordinal": 8,
"name": "created_at!",
"type_info": "Text"
"name": "created_at!: time::OffsetDateTime",
"type_info": "Timestamptz"
},
{
"ordinal": 9,
"name": "updated_at!",
"type_info": "Text"
"name": "updated_at!: time::OffsetDateTime",
"type_info": "Timestamptz"
},
{
"ordinal": 10,
"name": "published_at",
"type_info": "Text"
"name": "published_at: time::OffsetDateTime",
"type_info": "Timestamptz"
}
],
"parameters": {
@@ -73,10 +73,10 @@
false,
false,
true,
null,
null,
null
false,
false,
true
]
},
"hash": "b1bc2d2ab01d17d8f06a4956457e6b0bb560a808b4b5635a1c38e1ce6892cfb8"
"hash": "9625d5160a075b55b5bb980ea8e82e9da706a34d17adf0bc94ddb4e7a3818c18"
}
+15 -15
View File
@@ -2684,7 +2684,7 @@ impl AdminService {
)));
}
let now = now_string()?;
let now = OffsetDateTime::now_utc();
let agent_id = AgentId::new(new_prefixed_id("agent"));
let agent = Agent {
id: agent_id.clone(),
@@ -2695,8 +2695,8 @@ impl AdminService {
status: AgentStatus::Draft,
current_draft_version: 1,
latest_published_version: None,
created_at: now.clone(),
updated_at: now.clone(),
created_at: now,
updated_at: now,
published_at: None,
};
let version = AgentVersion {
@@ -2752,7 +2752,7 @@ impl AdminService {
)));
}
let updated_at = now_string()?;
let updated_at = OffsetDateTime::now_utc();
self.registry
.update_agent_summary(
workspace_id,
@@ -2767,7 +2767,7 @@ impl AdminService {
Ok(AgentMutationResult {
agent_id: agent_id.as_str().to_owned(),
workspace_id: workspace_id.as_str().to_owned(),
updated_at,
updated_at: format_timestamp(updated_at),
})
}
@@ -2790,7 +2790,7 @@ impl AdminService {
Ok(AgentMutationResult {
agent_id: agent_id.as_str().to_owned(),
workspace_id: workspace_id.as_str().to_owned(),
updated_at: existing.updated_at,
updated_at: format_timestamp(existing.updated_at),
})
}
@@ -2840,7 +2840,7 @@ impl AdminService {
agent_id: &AgentId,
version: u32,
) -> Result<PublishAgentResponse, ApiError> {
let published_at = now_string()?;
let published_at = OffsetDateTime::now_utc();
self.registry
.publish_agent(PublishAgentRequest {
workspace_id,
@@ -2856,7 +2856,7 @@ impl AdminService {
agent_id: agent_id.as_str().to_owned(),
workspace_id: workspace_id.as_str().to_owned(),
published_version: version,
published_at,
published_at: format_timestamp(published_at),
})
}
@@ -2867,7 +2867,7 @@ impl AdminService {
agent_id: &AgentId,
) -> Result<AgentMutationResult, ApiError> {
self.ensure_workspace_exists(workspace_id).await?;
let updated_at = now_string()?;
let updated_at = OffsetDateTime::now_utc();
self.registry
.unpublish_agent(workspace_id, agent_id, &updated_at)
.await?;
@@ -2876,7 +2876,7 @@ impl AdminService {
Ok(AgentMutationResult {
agent_id: agent_id.as_str().to_owned(),
workspace_id: workspace_id.as_str().to_owned(),
updated_at,
updated_at: format_timestamp(updated_at),
})
}
@@ -2887,7 +2887,7 @@ impl AdminService {
agent_id: &AgentId,
) -> Result<AgentMutationResult, ApiError> {
self.ensure_workspace_exists(workspace_id).await?;
let updated_at = now_string()?;
let updated_at = OffsetDateTime::now_utc();
self.registry
.archive_agent(workspace_id, agent_id, &updated_at)
.await?;
@@ -2896,7 +2896,7 @@ impl AdminService {
Ok(AgentMutationResult {
agent_id: agent_id.as_str().to_owned(),
workspace_id: workspace_id.as_str().to_owned(),
updated_at,
updated_at: format_timestamp(updated_at),
})
}
@@ -4946,9 +4946,9 @@ fn map_agent_summary_view(summary: AgentSummary) -> AgentSummaryView {
status: summary.status,
current_draft_version: summary.current_draft_version,
latest_published_version: summary.latest_published_version,
created_at: summary.created_at,
updated_at: summary.updated_at,
published_at: summary.published_at,
created_at: format_timestamp(summary.created_at),
updated_at: format_timestamp(summary.updated_at),
published_at: summary.published_at.map(format_timestamp),
operation_count: 0,
operation_ids: Vec::new(),
key_count: 0,
+4 -4
View File
@@ -1633,8 +1633,8 @@ mod tests {
status: AgentStatus::Draft,
current_draft_version: 1,
latest_published_version: None,
created_at: "2026-03-26T10:00:00Z".to_owned(),
updated_at: "2026-03-26T10:00:00Z".to_owned(),
created_at: OffsetDateTime::parse("2026-03-26T10:00:00Z", &Rfc3339).unwrap(),
updated_at: OffsetDateTime::parse("2026-03-26T10:00:00Z", &Rfc3339).unwrap(),
published_at: None,
};
let version = AgentVersion {
@@ -1643,7 +1643,7 @@ mod tests {
status: AgentStatus::Draft,
instructions: json!({}),
tool_selection_policy: json!({}),
created_at: "2026-03-26T10:00:00Z".to_owned(),
created_at: OffsetDateTime::parse("2026-03-26T10:00:00Z", &Rfc3339).unwrap(),
};
registry
@@ -1659,7 +1659,7 @@ mod tests {
workspace_id: &test_workspace_id(),
agent_id: &agent_id,
version: 1,
published_at: "2026-03-26T10:00:00Z",
published_at: &OffsetDateTime::parse("2026-03-26T10:00:00Z", &Rfc3339).unwrap(),
published_by: Some("alice"),
})
.await
+59 -4
View File
@@ -1,5 +1,6 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use time::OffsetDateTime;
use crate::ids::{AgentId, OperationId, WorkspaceId};
@@ -21,9 +22,12 @@ pub struct Agent {
pub status: AgentStatus,
pub current_draft_version: u32,
pub latest_published_version: Option<u32>,
pub created_at: String,
pub updated_at: String,
pub published_at: Option<String>,
#[serde(with = "time::serde::rfc3339")]
pub created_at: OffsetDateTime,
#[serde(with = "time::serde::rfc3339")]
pub updated_at: OffsetDateTime,
#[serde(with = "time::serde::rfc3339::option")]
pub published_at: Option<OffsetDateTime>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -33,7 +37,8 @@ pub struct AgentVersion {
pub status: AgentStatus,
pub instructions: Value,
pub tool_selection_policy: Value,
pub created_at: String,
#[serde(with = "time::serde::rfc3339")]
pub created_at: OffsetDateTime,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -45,3 +50,53 @@ pub struct AgentOperationBinding {
pub tool_description_override: Option<String>,
pub enabled: bool,
}
#[cfg(test)]
mod tests {
use serde_json::json;
use time::{OffsetDateTime, format_description::well_known::Rfc3339};
use super::{Agent, AgentStatus, AgentVersion};
use crate::ids::{AgentId, WorkspaceId};
#[test]
fn agent_serializes_timestamps_as_rfc3339() {
let agent = Agent {
id: AgentId::new("agent_01"),
workspace_id: WorkspaceId::new("ws_01"),
slug: "triage".to_owned(),
display_name: "Triage".to_owned(),
description: "Triage agent".to_owned(),
status: AgentStatus::Published,
current_draft_version: 2,
latest_published_version: Some(2),
created_at: OffsetDateTime::parse("2026-03-25T12:00:00Z", &Rfc3339).unwrap(),
updated_at: OffsetDateTime::parse("2026-03-25T12:05:00Z", &Rfc3339).unwrap(),
published_at: Some(OffsetDateTime::parse("2026-03-25T12:10:00Z", &Rfc3339).unwrap()),
};
let value = serde_json::to_value(&agent).unwrap();
assert_eq!(value["created_at"], json!("2026-03-25T12:00:00Z"));
assert_eq!(value["updated_at"], json!("2026-03-25T12:05:00Z"));
assert_eq!(value["published_at"], json!("2026-03-25T12:10:00Z"));
}
#[test]
fn agent_version_deserializes_created_at_from_rfc3339() {
let version: AgentVersion = serde_json::from_value(json!({
"agent_id": "agent_01",
"version": 1,
"status": "draft",
"instructions": {"system": "triage"},
"tool_selection_policy": {"mode": "allow_list"},
"created_at": "2026-03-25T12:00:00Z"
}))
.unwrap();
assert_eq!(
version.created_at,
OffsetDateTime::parse("2026-03-25T12:00:00Z", &Rfc3339).unwrap()
);
}
}
+5 -5
View File
@@ -170,9 +170,9 @@ pub struct AgentSummary {
pub status: AgentStatus,
pub current_draft_version: u32,
pub latest_published_version: Option<u32>,
pub created_at: String,
pub updated_at: String,
pub published_at: Option<String>,
pub created_at: OffsetDateTime,
pub updated_at: OffsetDateTime,
pub published_at: Option<OffsetDateTime>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@@ -181,7 +181,7 @@ pub struct AgentVersionRecord {
pub workspace_id: WorkspaceId,
pub version: u32,
pub status: AgentStatus,
pub created_at: String,
pub created_at: OffsetDateTime,
pub snapshot: AgentVersion,
pub bindings: Vec<AgentOperationBinding>,
}
@@ -419,7 +419,7 @@ pub struct PublishAgentRequest<'a> {
pub workspace_id: &'a WorkspaceId,
pub agent_id: &'a AgentId,
pub version: u32,
pub published_at: &'a str,
pub published_at: &'a OffsetDateTime,
pub published_by: Option<&'a str>,
}
+13 -13
View File
@@ -15,9 +15,9 @@ 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(published_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as published_at
created_at as \"created_at!: time::OffsetDateTime\",
updated_at as \"updated_at!: time::OffsetDateTime\",
published_at as \"published_at: time::OffsetDateTime\"
from agents
where workspace_id = $1
order by slug asc",
@@ -60,9 +60,9 @@ 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(published_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as published_at
created_at as \"created_at!: time::OffsetDateTime\",
updated_at as \"updated_at!: time::OffsetDateTime\",
published_at as \"published_at: time::OffsetDateTime\"
from agents
where workspace_id = $1 and id = $2",
workspace_id.as_str(),
@@ -117,9 +117,9 @@ impl PostgresRegistry {
.bind(serialize_enum_text(&request.agent.status, "status")?)
.bind(to_db_version(request.agent.current_draft_version))
.bind(request.agent.latest_published_version.map(to_db_version))
.bind(&request.agent.created_at)
.bind(&request.agent.updated_at)
.bind(request.agent.published_at.as_deref())
.bind(request.agent.created_at)
.bind(request.agent.updated_at)
.bind(request.agent.published_at)
.execute(&mut *tx)
.await?;
@@ -154,7 +154,7 @@ impl PostgresRegistry {
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!\"
created_at as \"created_at!: time::OffsetDateTime\"
from agent_versions
where agent_id = $1 and version = $2",
agent_id.as_str(),
@@ -212,7 +212,7 @@ impl PostgresRegistry {
slug: &str,
display_name: &str,
description: &str,
updated_at: &str,
updated_at: &time::OffsetDateTime,
) -> Result<(), RegistryError> {
let result = sqlx::query(
"update agents
@@ -331,7 +331,7 @@ impl PostgresRegistry {
&self,
workspace_id: &WorkspaceId,
agent_id: &AgentId,
updated_at: &str,
updated_at: &time::OffsetDateTime,
) -> Result<(), RegistryError> {
let mut tx = self.pool.begin().await?;
@@ -384,7 +384,7 @@ impl PostgresRegistry {
&self,
workspace_id: &WorkspaceId,
agent_id: &AgentId,
updated_at: &str,
updated_at: &time::OffsetDateTime,
) -> Result<(), RegistryError> {
let mut tx = self.pool.begin().await?;
+11 -11
View File
@@ -414,7 +414,7 @@ async fn insert_agent_version_row(
.bind(serialize_enum_text(&version.status, "status")?)
.bind(Json(version.instructions.clone()))
.bind(Json(version.tool_selection_policy.clone()))
.bind(&version.created_at)
.bind(version.created_at)
.execute(&mut **tx)
.await?;
@@ -684,9 +684,9 @@ fn build_agent_summary(
status: String,
current_draft_version: i32,
latest_published_version: Option<i32>,
created_at: String,
updated_at: String,
published_at: Option<String>,
created_at: OffsetDateTime,
updated_at: OffsetDateTime,
published_at: Option<OffsetDateTime>,
) -> Result<AgentSummary, RegistryError> {
Ok(AgentSummary {
id: AgentId::new(id),
@@ -798,7 +798,7 @@ fn build_agent_version_record(
status: String,
instructions_json: Value,
tool_selection_policy_json: Value,
created_at: String,
created_at: OffsetDateTime,
bindings: Vec<AgentOperationBinding>,
) -> Result<AgentVersionRecord, RegistryError> {
let version = from_db_version(version, "version")?;
@@ -809,7 +809,7 @@ fn build_agent_version_record(
workspace_id: summary.workspace_id.clone(),
version,
status,
created_at: created_at.clone(),
created_at,
bindings,
snapshot: AgentVersion {
agent_id: summary.id.clone(),
@@ -1842,7 +1842,7 @@ mod tests {
workspace_id: &test_workspace_id(),
agent_id: &agent.id,
version: version.version,
published_at: "2026-03-25T12:11:00Z",
published_at: &timestamp("2026-03-25T12:11:00Z"),
published_by: Some("alice"),
})
.await
@@ -1922,7 +1922,7 @@ mod tests {
workspace_id: &test_workspace_id(),
agent_id: &agent.id,
version: version.version,
published_at: "2026-03-25T12:11:00Z",
published_at: &timestamp("2026-03-25T12:11:00Z"),
published_by: Some("alice"),
})
.await
@@ -2449,8 +2449,8 @@ mod tests {
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(),
created_at: timestamp("2026-03-25T11:58:00Z"),
updated_at: timestamp("2026-03-25T12:00:00Z"),
published_at: None,
}
}
@@ -2468,7 +2468,7 @@ mod tests {
"mode": "allow_list",
"max_tools": 8
}),
created_at: "2026-03-25T12:00:00Z".to_owned(),
created_at: timestamp("2026-03-25T12:00:00Z"),
}
}