use super::*;
use sqlmodel_core::Value;
use std::collections::BTreeSet;
const PAGE_SIZE: usize = 256;
#[derive(Clone, Copy)]
enum ReadIntent<'a> {
Public,
Record(&'a DecisionFields),
}
fn read_error() -> DomainError {
decide_storage_error("Could not read a coherent decision snapshot; no partial result returned")
}
pub(super) fn load(
scope: &mut DecideScope,
include_superseded: bool,
now: DateTime<Utc>,
) -> Result<Vec<DecideItem>, DomainError> {
if !scope.database_path.exists() {
return Ok(Vec::new());
}
let connection = open_decide_database_read_only(&scope.database_path)?;
read_with_observer(&connection, scope, include_superseded, now, |_| Ok(()))
}
pub(super) fn load_record_heads(
scope: &mut DecideScope,
fields: &DecisionFields,
now: DateTime<Utc>,
) -> Result<Vec<DecideItem>, DomainError> {
if !scope.database_path.exists() {
return Ok(Vec::new());
}
let connection = open_decide_database_read_only(&scope.database_path)?;
let snapshot = ReadSnapshot::begin(&connection)?;
bind_scope(&connection, scope)?;
let heads = record_heads_in_current_snapshot(&connection, &scope.workspace_id, fields, now)?;
snapshot.finish()?;
Ok(heads)
}
pub(super) fn record_heads_in_current_snapshot(
connection: &DbConnection,
workspace_id: &str,
fields: &DecisionFields,
now: DateTime<Utc>,
) -> Result<Vec<DecideItem>, DomainError> {
read_current_snapshot(
connection,
workspace_id,
false,
now,
ReadIntent::Record(fields),
|_| Ok(()),
)
}
fn bind_scope(connection: &DbConnection, scope: &mut DecideScope) -> Result<(), DomainError> {
scope.workspace_id = bound_workspace_id_or_hash(
connection,
&scope.workspace_id,
&[scope.workspace_path.as_path()],
)
.map_err(|_| read_error())?;
Ok(())
}
fn read_with_observer(
connection: &DbConnection,
scope: &mut DecideScope,
include_superseded: bool,
now: DateTime<Utc>,
before_hydration: impl FnMut(&[&str]) -> Result<(), DomainError>,
) -> Result<Vec<DecideItem>, DomainError> {
let snapshot = ReadSnapshot::begin(connection)?;
bind_scope(connection, scope)?;
let decisions = read_current_snapshot(
connection,
&scope.workspace_id,
include_superseded,
now,
ReadIntent::Public,
before_hydration,
)?;
snapshot.finish()?;
Ok(decisions)
}
fn read_current_snapshot(
connection: &DbConnection,
workspace_id: &str,
include_superseded: bool,
now: DateTime<Utc>,
intent: ReadIntent<'_>,
mut before_hydration: impl FnMut(&[&str]) -> Result<(), DomainError>,
) -> Result<Vec<DecideItem>, DomainError> {
let mut after = String::new();
let mut decisions = Vec::new();
let mut lineage = lineage::DecisionLineage::new(workspace_id);
let mut closed = None;
loop {
let rows = connection
.query(
"SELECT id FROM memories WHERE workspace_id = ?1 AND kind = 'decision' AND tombstoned_at IS NULL AND (?2 = 1 OR superseded_at IS NULL) AND id > ?3 ORDER BY id ASC LIMIT ?4",
&[
Value::Text(workspace_id.to_owned()),
Value::BigInt(if include_superseded { 1 } else { 0 }),
Value::Text(after.clone()),
Value::BigInt(PAGE_SIZE as i64),
],
)
.map_err(|_| read_error())?;
if rows.is_empty() {
break;
}
if rows.len() > PAGE_SIZE {
return Err(read_error());
}
if closed.is_none() {
closed = Some(
crate::core::memory_lifecycle::load_memory_seals_for_admission(
connection,
workspace_id,
)
.map_err(|_| read_error())?
.into_iter()
.filter(|seal| seal.is_sealed())
.map(|seal| seal.memory_id)
.collect::<BTreeSet<_>>(),
);
}
let closed = closed.as_ref().ok_or_else(read_error)?;
let mut ids = Vec::with_capacity(rows.len());
for row in &rows {
let id = row.get(0).and_then(Value::as_str).ok_or_else(read_error)?;
if id <= after.as_str() {
return Err(read_error());
}
after = id.to_owned();
ids.push(id);
}
let held = crate::core::memory_lifecycle::pending_memory_review_ids(
connection,
workspace_id,
&ids,
)
.map_err(|_| read_error())?;
match intent {
ReadIntent::Public => {
ids.retain(|id| !closed.contains(*id) && !held.contains(*id));
}
ReadIntent::Record(_) => {
if ids.iter().any(|id| closed.contains(*id)) {
return Err(record_authority_error());
}
}
}
if ids.is_empty() {
if rows.len() < PAGE_SIZE {
break;
}
continue;
}
before_hydration(&ids)?;
let mut memories = connection
.get_memories_batch(&ids)
.map_err(|_| read_error())?;
let mut eligible = Vec::with_capacity(ids.len());
for id in ids {
let memory = memories.remove(id).ok_or_else(read_error)?;
if memory.id != id
|| memory.workspace_id != workspace_id
|| memory.kind != "decision"
|| memory.tombstoned_at.is_some()
{
return Err(read_error());
}
if memory.content == crate::models::MEMORY_SEAL_PLACEHOLDER_CONTENT {
match intent {
ReadIntent::Public => continue,
ReadIntent::Record(_) => return Err(record_authority_error()),
}
}
if let ReadIntent::Record(fields) = intent {
let topic =
topic_from_content(&memory.content).unwrap_or_else(|| memory.content.clone());
let related = fields.supersedes.as_deref() == Some(id)
|| normalize_decision_topic(&topic) == fields.normalized_topic;
if !related {
continue;
}
if held.contains(id) {
return Err(record_authority_error());
}
}
eligible.push(memory);
}
let roots: Vec<_> = eligible.iter().map(|memory| memory.id.as_str()).collect();
lineage.load(connection, &roots)?;
for memory in eligible {
let item = memory_to_decide_item(connection, &memory, now, lineage.depth(&memory.id)?)
.map_err(|_| read_error())?;
if !include_superseded && item.superseded {
return Err(read_error());
}
decisions.push(item);
}
if rows.len() < PAGE_SIZE {
break;
}
}
Ok(decisions)
}
fn record_authority_error() -> DomainError {
DomainError::PolicyDenied {
message: "Current decision source authority prevents recording or replacing this topic; no decision was recorded".to_owned(),
repair: Some(
"Review pending decision feedback or explicitly reveal sealed current decisions, then retry. A hidden head is not an unused topic.".to_owned(),
),
}
}
struct ReadSnapshot<'a> {
connection: &'a DbConnection,
active: bool,
}
impl<'a> ReadSnapshot<'a> {
fn begin(connection: &'a DbConnection) -> Result<Self, DomainError> {
connection.begin_read_snapshot().map_err(|_| read_error())?;
Ok(Self {
connection,
active: true,
})
}
fn finish(mut self) -> Result<(), DomainError> {
self.connection
.commit_read_snapshot()
.map_err(|_| read_error())?;
self.active = false;
Ok(())
}
}
impl Drop for ReadSnapshot<'_> {
fn drop(&mut self) {
if self.active && self.connection.rollback_read_snapshot().is_err() {
tracing::error!("failed to release the owned decision read snapshot");
}
}
}
#[cfg(test)]
#[path = "decide_read_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "decide_admission_tests.rs"]
mod admission_tests;