use crate::contracts::assemble::assemble;
use crate::contracts::availability::{SchemaAvailability, SchemaFreshness};
use crate::contracts::retrieval::{self, RetrievalPolicy};
use crate::contracts::selection::select;
use crate::contracts::view::{RecallDiagnostics, RecallOutcome};
use saya_store::SqliteStateStore;
use saya_types::{DatabaseObjectRef, ProfileIdentity};
#[derive(Debug, Clone, Copy)]
pub(crate) struct RecallBounds {
pub max_objects: usize,
pub max_claims_per_object: usize,
pub max_bytes: usize,
}
impl Default for RecallBounds {
fn default() -> Self {
Self::defaults()
}
}
impl RecallBounds {
pub(crate) const fn defaults() -> Self {
Self {
max_objects: 5,
max_claims_per_object: 12,
max_bytes: 16384,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) enum RecallMode {
#[default]
Confirmed,
IncludeCandidates,
}
impl RecallMode {
pub(crate) fn admits_state(self, state: saya_types::KnowledgeState) -> bool {
use saya_types::KnowledgeState;
match self {
Self::Confirmed => matches!(state, KnowledgeState::Active),
Self::IncludeCandidates => {
matches!(state, KnowledgeState::Active | KnowledgeState::Pending)
}
}
}
}
pub(crate) struct RecallRequest<'a> {
pub profiles: &'a [ProfileIdentity],
pub explicit_refs: &'a [DatabaseObjectRef],
pub terms: &'a [String],
pub allow_database_context: bool,
pub schemas: &'a [(ProfileIdentity, SchemaAvailability)],
pub now_unix_ms: i64,
pub bounds: RecallBounds,
pub recall_mode: RecallMode,
pub admit_candidate: Option<saya_types::ClaimId>,
pub policy: RetrievalPolicy,
}
pub(crate) async fn recall(store: &SqliteStateStore, request: RecallRequest<'_>) -> RecallOutcome {
let selection = match select(store, &request, request.schemas).await {
Ok(s) => s,
Err(_) => {
return empty(RecallDiagnostics {
attempted: true,
store_unavailable: true,
..default_diag()
});
}
};
let mut diag = RecallDiagnostics {
attempted: true,
considered: selection.considered,
excluded_by_status: selection.excluded_by_status,
..default_diag()
};
if !request.allow_database_context {
diag.excluded_by_privacy = selection.candidates.len();
return empty(diag);
}
let assembled = assemble(
&selection.candidates,
request.schemas,
request.bounds,
freshness_for(request.policy, request.now_unix_ms),
&mut diag,
);
let contracts = retrieval::apply(assembled, request.policy, &mut diag);
diag.selected = contracts.len();
RecallOutcome {
contracts,
diagnostics: diag,
}
}
fn default_diag() -> RecallDiagnostics {
RecallDiagnostics::default()
}
fn empty(diag: RecallDiagnostics) -> RecallOutcome {
RecallOutcome {
contracts: Vec::new(),
diagnostics: diag,
}
}
fn freshness_for(policy: RetrievalPolicy, now_unix_ms: i64) -> SchemaFreshness {
match policy {
RetrievalPolicy::ForModel => SchemaFreshness::for_model(now_unix_ms),
RetrievalPolicy::ForHumanReview => SchemaFreshness::Unbounded,
}
}