MCPcopy Create free account
hub / github.com/cortexkit/magic-context / update_memory_status

Function update_memory_status

packages/dashboard/src-tauri/src/db.rs:3245–3313  ·  view source on GitHub ↗
(
    conn: &mut Connection,
    memory_id: i64,
    new_status: &str,
)

Source from the content-addressed store, hash-verified

3243}
3244
3245pub fn update_memory_status(
3246 conn: &mut Connection,
3247 memory_id: i64,
3248 new_status: &str,
3249) -> Result<(), rusqlite::Error> {
3250 // Reject any status outside the canonical set before touching the DB. A
3251 // malformed call setting status="archive" (vs "archived") or any free string
3252 // would make the memory vanish from active/permanent/archived logic with no
3253 // valid epoch/delta interpretation.
3254 if !matches!(new_status, "active" | "permanent" | "archived") {
3255 return Err(rusqlite::Error::SqliteFailure(
3256 rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_CONSTRAINT),
3257 Some(format!(
3258 "Invalid memory status '{new_status}' (expected active, permanent, or archived)."
3259 )),
3260 ));
3261 }
3262 // Phase A: resolve the target row before opening a write transaction.
3263 let target = lookup_memory_mutation_target(conn, memory_id)?;
3264 // Any transition that CHANGES which memories enter or how they rank in the
3265 // m[0] baseline must bump the epoch to invalidate cached m[0]. That covers
3266 // every status change into an injectable status (`active`/`permanent`) from a
3267 // different status — including archived->active/permanent (restore) AND
3268 // active<->permanent (pin/unpin), since memory selection is permanent-first
3269 // under budget pressure, so pinning reorders the rendered set. Gating only on
3270 // archived-origin left active<->permanent invalidating nothing.
3271 let prior_status = target.status.as_deref();
3272 let into_injectable = new_status == "active" || new_status == "permanent";
3273 let needs_epoch_bump = into_injectable && prior_status != Some(new_status);
3274 let project_identity = if needs_epoch_bump {
3275 Some(normalize_stored_project_path(&target.project_path))
3276 } else {
3277 None
3278 };
3279 let is_archive = new_status == "archived" && prior_status != Some("archived");
3280
3281 // Phase B: re-check the target row, mutate, and queue/bump in one tx.
3282 let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
3283 verify_memory_project_path_unchanged(&tx, memory_id, &target.project_path)?;
3284 tx.execute(
3285 "UPDATE memories SET status = ?1, updated_at = ?2 WHERE id = ?3",
3286 params![new_status, now_millis(), memory_id],
3287 )?;
3288 // Resolve workspace fan-out INSIDE the write transaction so a concurrent
3289 // add-member committing between Phase A and here can't leave a new member's
3290 // epoch un-bumped (it would then miss this restored memory).
3291 let epoch_bump_identities = if let Some(ref id) = project_identity {
3292 Some(crate::workspaces::workspace_member_identities_for_project(
3293 &tx, id,
3294 )?)
3295 } else {
3296 None
3297 };
3298 if let Some(identities) = epoch_bump_identities.as_ref() {
3299 crate::workspaces::bump_epochs_for_identities(&tx, identities)?;
3300 } else if is_archive {
3301 queue_memory_mutation(
3302 &tx,