Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ if validation.iter().all(|candidate| candidate.status == CandidateValidationStat
}
```

`commit` revalidates the plan before writing. Graph-authoritative objects, links, provenance, lifecycle, and currentness are critical writes; vector indexing and retrieval-stat updates are repairable and are reported in `RememberOutcome`.
`commit` revalidates the plan before writing. Graph-authoritative objects, links, provenance, lifecycle, and currentness are critical writes; vector indexing and retrieval-stat updates are repairable and are reported in `RememberOutcome`. Failed graph reads feeding the stats projection, including endpoint hydration and currency lookup, report `StatsUpdateCause::GraphRead`; the nested `GraphQueryError` retains the underlying failure.

For callers that want the standard write lifecycle in one call, `remember(RememberInput, RememberOptions)` composes `prepare`, `validate_plan`, and `commit` over the same graph-authoritative machinery.

Expand Down
2 changes: 2 additions & 0 deletions docs/design/database/graph_schema_design.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ The policy combination that includes suppressed memories while excluding superse

Graph writes are critical. Vector and stats updates are repairable parts of the write outcome, with typed failures and affected IDs. A graph-only memory loses semantic recall until vector indexing is repaired; stale candidates are checked against graph existence and lifecycle before inclusion. Replaying an old plan does not re-index an interpreted memory that graph authority identifies as superseded.

A failed graph read feeding the stats projection is reported as `StatsUpdateCause::GraphRead` and retained in unhealthy stats as `RetrievalStatsHealthCause::GraphRead`. Both carry the underlying `GraphQueryError`, whether the read was endpoint hydration or currency lookup.

The library has no cross-store reconciliation or stats-rebuild operation. Stats health remains unhealthy after an internal failure; recovery requires a fresh store and caller-managed replay. Raw-source resolution and cross-store census operations belong to callers or operators.

See the [schema cheat sheet](schema_cheat_sheet.md) for predicate tables and the [vector payload contract](vector_payload_design.md) for recall record fields.
16 changes: 7 additions & 9 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,8 @@ pub enum VectorIndexingCause {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Error)]
#[serde(tag = "cause", rename_all = "snake_case")]
pub enum StatsUpdateCause {
#[error("stats endpoint hydration failed: {error}")]
EndpointHydration { error: GraphQueryError },
#[error("graph read for stats projection failed: {error}")]
GraphRead { error: GraphQueryError },
#[error("stats edge write failed: {error}")]
EdgeWrite { error: RetrievalStatsStoreError },
#[error("stats object-state write failed: {error}")]
Expand Down Expand Up @@ -274,8 +274,8 @@ pub enum RetrievalStatsStoreError {
pub enum RetrievalStatsHealthCause {
#[error("stats store initialization failed: {error}")]
StoreInitialization { error: RetrievalStatsStoreError },
#[error("stats endpoint hydration failed: {error}")]
EndpointHydration { error: GraphQueryError },
#[error("graph read for stats projection failed: {error}")]
GraphRead { error: GraphQueryError },
#[error("stats edge write failed: {error}")]
EdgeWrite { error: RetrievalStatsStoreError },
#[error("stats object-state write failed: {error}")]
Expand All @@ -291,11 +291,9 @@ pub enum RetrievalStatsHealthCause {
impl StatsUpdateCause {
pub(crate) fn health_cause(&self) -> Option<RetrievalStatsHealthCause> {
match self {
Self::EndpointHydration { error } => {
Some(RetrievalStatsHealthCause::EndpointHydration {
error: error.clone(),
})
}
Self::GraphRead { error } => Some(RetrievalStatsHealthCause::GraphRead {
error: error.clone(),
}),
Self::EdgeWrite { error } => Some(RetrievalStatsHealthCause::EdgeWrite {
error: error.clone(),
}),
Expand Down
21 changes: 19 additions & 2 deletions src/usecases/remember.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,13 +419,30 @@ mod tests {
outcome
.stats_update_status
.failure
.as_ref()
.unwrap()
.causes
.as_slice(),
[StatsUpdateCause::EndpointHydration {
[StatsUpdateCause::GraphRead {
error: crate::errors::GraphQueryError::Selection { .. }
}]
));
assert_eq!(
serde_json::to_value(&outcome.stats_update_status.failure.as_ref().unwrap().causes[0])
.unwrap()["cause"],
"graph_read"
);
let health_cause = stats.health().await.unwrap().last_error_cause.unwrap();
assert!(matches!(
health_cause,
RetrievalStatsHealthCause::GraphRead {
error: crate::errors::GraphQueryError::Selection { .. }
}
));
assert_eq!(
serde_json::to_value(&health_cause).unwrap()["operation"],
"graph_read"
);
assert!(outcome
.repair_needed
.iter()
Expand Down Expand Up @@ -862,7 +879,7 @@ mod tests {
assert!(matches!(
failure.causes.as_slice(),
[
StatsUpdateCause::EndpointHydration { .. },
StatsUpdateCause::GraphRead { .. },
StatsUpdateCause::EdgeWrite { .. }
]
));
Expand Down
10 changes: 5 additions & 5 deletions src/usecases/stats_projection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ where
let causes = if missing_endpoint_refs.is_empty() {
Vec::new()
} else {
vec![StatsUpdateCause::EndpointHydration {
vec![StatsUpdateCause::GraphRead {
error: GraphQueryError::Hydration {
detail: format_missing_endpoint_refs(&missing_endpoint_refs),
},
Expand All @@ -84,7 +84,7 @@ where
}
Err(error) => (
objects.to_vec(),
vec![StatsUpdateCause::EndpointHydration { error }],
vec![StatsUpdateCause::GraphRead { error }],
),
}
};
Expand Down Expand Up @@ -138,7 +138,7 @@ where
.await
{
Ok(ids) => ids.into_iter().collect(),
Err(error) => return vec![StatsUpdateCause::EndpointHydration { error }],
Err(error) => return vec![StatsUpdateCause::GraphRead { error }],
}
};
let states = retrieval_stats_object_states(objects, &superseded);
Expand Down Expand Up @@ -509,13 +509,13 @@ mod tests {
);
assert!(matches!(
failure.causes.as_slice(),
[StatsUpdateCause::EndpointHydration {
[StatsUpdateCause::GraphRead {
error: GraphQueryError::Hydration { .. },
}]
));
assert!(matches!(
stats_store.marked_causes.lock().unwrap().as_slice(),
[RetrievalStatsHealthCause::EndpointHydration {
[RetrievalStatsHealthCause::GraphRead {
error: GraphQueryError::Hydration { .. },
}]
));
Expand Down
2 changes: 1 addition & 1 deletion tests/write_planning_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ async fn authority_split_outcome_fields_are_coherent_on_healthy_commit() {
assert!(matches!(
failure.causes.as_slice(),
[
StatsUpdateCause::EndpointHydration { .. }
StatsUpdateCause::GraphRead { .. }
| StatsUpdateCause::EdgeWrite { .. }
| StatsUpdateCause::ObjectStateWrite { .. }
| StatsUpdateCause::HealthCheck { .. }
Expand Down
Loading