a2a_protocol_server/store/
mod.rs1pub mod retention;
9pub mod task_store;
10pub mod tenant;
11pub mod terminal;
12
13#[cfg(any(feature = "sqlite", feature = "postgres"))]
15pub(crate) mod cursor;
16
17pub(crate) mod pagination;
19
20#[cfg(feature = "sqlite")]
21pub mod migration;
22#[cfg(feature = "sqlite")]
23pub mod sqlite_store;
24#[cfg(feature = "sqlite")]
25pub mod tenant_sqlite_store;
26
27#[cfg(feature = "postgres")]
28pub mod pg_migration;
29#[cfg(feature = "postgres")]
30pub(crate) mod pg_schema;
31#[cfg(feature = "postgres")]
32pub mod postgres_store;
33#[cfg(feature = "postgres")]
34pub mod tenant_postgres_store;
35
36#[cfg(any(feature = "sqlite", feature = "postgres"))]
38mod tenant_idempotency;
39
40#[cfg(any(feature = "sqlite", feature = "postgres"))]
42mod event_log_sql;
43#[cfg(any(feature = "sqlite", feature = "postgres"))]
44mod tenant_event_log;
45
46pub use retention::{PurgeReport, RetentionPolicy, terminal_states};
47pub use task_store::{
53 ArtifactDelta, DEFAULT_IDEMPOTENCY_KEY_TTL, DEFAULT_MAX_EVENTS_PER_TASK, DEFAULT_MAX_PAGE_SIZE,
54 IdempotencyClaim, InMemoryTaskStore, RecordedEvent, TaskStore, TaskStoreConfig,
55};
56pub use tenant::{TenantAwareInMemoryTaskStore, TenantContext, TenantStoreConfig};
57pub use terminal::{TERMINAL_STATE_CONFLICT_MARKER, TerminalStateConflict, refuses_write};
58
59#[cfg(feature = "sqlite")]
72pub(crate) fn status_timestamp_sqlite(ts: Option<&str>) -> Option<String> {
73 let millis = ts.and_then(a2a_protocol_types::parse_iso8601_to_unix_millis)?;
74 let iso = a2a_protocol_types::unix_millis_to_iso8601(millis);
75 Some(format!("{} {}", &iso[..10], &iso[11..23]))
77}
78
79#[cfg(feature = "postgres")]
84pub(crate) fn status_timestamp_rfc3339(ts: Option<&str>) -> Option<String> {
85 let millis = ts.and_then(a2a_protocol_types::parse_iso8601_to_unix_millis)?;
86 Some(a2a_protocol_types::unix_millis_to_iso8601(millis))
87}
88
89#[cfg(feature = "sqlite")]
90pub use migration::{Migration, MigrationRunner};
91#[cfg(feature = "sqlite")]
92pub use sqlite_store::SqliteTaskStore;
93#[cfg(feature = "sqlite")]
94pub use tenant_sqlite_store::TenantAwareSqliteTaskStore;
95
96#[cfg(feature = "postgres")]
97pub use pg_migration::{BUILTIN_PG_MIGRATIONS, PgMigration, PgMigrationRunner};
98#[cfg(feature = "postgres")]
99pub use postgres_store::PostgresTaskStore;
100#[cfg(feature = "postgres")]
101pub use tenant_postgres_store::TenantAwarePostgresTaskStore;
102
103#[cfg(test)]
104mod status_timestamp_tests {
105 #[cfg(feature = "sqlite")]
112 #[test]
113 fn sqlite_shape_is_the_column_format() {
114 assert_eq!(
115 super::status_timestamp_sqlite(Some("2026-03-15T12:00:00.123Z")).as_deref(),
116 Some("2026-03-15 12:00:00.123"),
117 );
118 assert_eq!(
121 super::status_timestamp_sqlite(Some("2026-03-15T12:00:00Z")).as_deref(),
122 Some("2026-03-15 12:00:00.000"),
123 );
124 }
125
126 #[cfg(feature = "sqlite")]
127 #[test]
128 fn sqlite_returns_none_for_missing_or_unparseable() {
129 assert_eq!(super::status_timestamp_sqlite(None), None);
130 assert_eq!(super::status_timestamp_sqlite(Some("")), None);
131 assert_eq!(
132 super::status_timestamp_sqlite(Some("not a timestamp")),
133 None
134 );
135 assert_eq!(super::status_timestamp_sqlite(Some("2026-03-15")), None);
136 }
137
138 #[cfg(feature = "sqlite")]
139 #[test]
140 fn sqlite_slicing_survives_out_of_range_years() {
141 for input in [
146 "99999-01-01T00:00:00Z",
147 "999999-12-31T23:59:59.999Z",
148 "1969-12-31T23:59:59Z",
149 "0001-01-01T00:00:00Z",
150 "+010000-01-01T00:00:00Z",
151 ] {
152 let out = super::status_timestamp_sqlite(Some(input));
153 if let Some(s) = out {
154 assert!(
155 s.is_char_boundary(0) && s.len() >= 23,
156 "{input} produced a malformed value: {s:?}"
157 );
158 }
159 }
160 }
161
162 #[cfg(feature = "postgres")]
163 #[test]
164 fn rfc3339_normalizes_to_canonical_utc() {
165 assert_eq!(
166 super::status_timestamp_rfc3339(Some("2026-03-15T12:00:00.123Z")).as_deref(),
167 Some("2026-03-15T12:00:00.123Z"),
168 );
169 assert_eq!(
173 super::status_timestamp_rfc3339(Some("2026-03-15T14:00:00+02:00")).as_deref(),
174 Some("2026-03-15T12:00:00.000Z"),
175 );
176 assert_eq!(super::status_timestamp_rfc3339(None), None);
177 assert_eq!(super::status_timestamp_rfc3339(Some("nope")), None);
178 }
179}