Skip to content

apomesh-state-store-smartchat-postgres

The Postgres adapter behind SmartChat’s SmartChatStore port — the durable home for operator-chat conversations and their turns. It is a sibling to apomesh-state-store-postgres: same crate-direct sqlx layout, same inline migration-runner pattern, but a disjoint schema (SmartChat’s tables stay SmartChat’s, the substrate’s stay the substrate’s). Reach for this crate when a deployment needs SmartChat conversations to survive a daemon restart; without a Postgres pool the daemon falls back to the in-memory adapter.

  • Layer: state stores — the durable adapter behind SmartChat’s store port, not a substrate StateStore. It implements the SmartChatStore trait owned by apomesh-smartchat.
  • Depends on: apomesh-smartchat (the SmartChatStore trait plus the Conversation / Turn domain types) and apomesh-substrate (TenantId, TenantContext for tenant-scoped queries).
  • Consumed by: apomesh-orchestrator-bin — the only consumer; it selects this adapter at boot when a Postgres pool is present.
  • PostgresSmartChatStore — the durable SmartChatStore implementation. Every query is tenant-scoped (tenant_id in the WHERE clause), and a cross-tenant or missing row folds into ConversationNotFound so the adapter never leaks cross-tenant existence.
  • Embedded, independently-tracked migrations — a MIGRATIONS const embeds each migrations/*.sql via include_str!; run_migrations applies them transactionally and idempotently, tracked in a _smartchat_migrations table kept separate from the substrate’s _apomesh_migrations so the two schemas evolve on their own timelines.
  • Crate-direct sqlx — the crate goes straight to sqlx-core / sqlx-postgres (no umbrella sqlx crate) for the same reason the substrate’s Postgres adapter does: to avoid the umbrella’s transitive sqlx-sqlite colliding with the substrate’s rusqlite.
  • Shared-pool constructionfrom_pool wraps an existing Arc<PgPool> so the orchestrator can share one connection pool with the substrate’s state-store adapter and keep connection counts bounded.

Verified against lib.rs:

  • PostgresSmartChatStore::connect(url, pool_size) -> Result<Self, PostgresSmartChatError> — open a fresh pool for the adapter.
  • PostgresSmartChatStore::from_pool(Arc<PgPool>) -> Self — the production path: share the process-wide pool.
  • run_migrations() -> Result<(), PostgresSmartChatError> — apply the embedded SQL; idempotent.
  • pool() -> &Arc<PgPool> — borrow the underlying pool (test probing).
  • The SmartChatStore trait methods (create_conversation, get_conversation, list_conversations, rename_conversation, delete_conversation, append_turn, set_turn_outcome, list_turns) are implemented here; their contracts live on the apomesh-smartchat trait.
  • PostgresSmartChatError — construction/migration errors (Connect, Migration); runtime query errors surface as SmartChatError::Store.

The daemon selects the durable adapter when a Postgres pool is available, falling back to the in-memory store otherwise:

use std::sync::Arc;
use apomesh_smartchat::{InMemorySmartChatStore, SmartChatStore};
use apomesh_state_store_smartchat_postgres::PostgresSmartChatStore;
use sqlx_postgres::PgPool;
# async fn wire(pool: Option<Arc<PgPool>>) -> anyhow::Result<()> {
let store: Arc<dyn SmartChatStore> = match pool {
Some(pool) => {
let store = PostgresSmartChatStore::from_pool(pool);
store.run_migrations().await?;
Arc::new(store)
}
None => Arc::new(InMemorySmartChatStore::new()),
};
# let _ = store;
# Ok(())
# }