Skip to content
Merged
Show file tree
Hide file tree
Changes from 85 commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
9d948ed
refactor(memory): update memory trait
senamakel Aug 29, 2026
5e7e263
test(namespace-store): update document store tests
senamakel Aug 29, 2026
92cc60c
chore(namespace-store): update initialization logic
senamakel Aug 29, 2026
96fdcd6
chore: update namespace store initialization
senamakel Aug 29, 2026
64f0a97
test: correct cross-session alias test name
senamakel Aug 29, 2026
8183f10
chore: update namespace store initialization
senamakel Aug 29, 2026
99ce5d6
chore: update namespace store initialization
senamakel Aug 29, 2026
b150898
fix(namespace): preserve logical namespace labels
senamakel Aug 29, 2026
4210ceb
fix(store): persist logical namespaces in document upserts
senamakel Aug 29, 2026
c9005fe
fix(store): use logical namespaces in memory summaries
senamakel Aug 29, 2026
d2e65f6
build(conformance): add tinymemory-bus dependency
senamakel Aug 29, 2026
3566142
chore(suite): import the namespace type
senamakel Aug 29, 2026
18ac14d
test(conformance): check section preservation in namespaces
senamakel Aug 29, 2026
bcb2a50
test(conformance): run namespace section preservation assertion
senamakel Aug 29, 2026
344b8b8
feat(conformance): expose namespace section assertion
senamakel Aug 29, 2026
7c51282
chore: update locked dependencies
senamakel Aug 29, 2026
e99122c
test(conformance): add scratch red check
senamakel Aug 29, 2026
d5b6e5e
chore(conformance): remove scratch namespace bug check
senamakel Aug 29, 2026
6ede5ba
test(reference): simulate namespace sanitization bug
senamakel Aug 29, 2026
fa0a677
fix(conformance): preserve namespace names in summaries
senamakel Aug 29, 2026
1ed5c9d
chore(conformance): remove unnecessary bus dependency
senamakel Aug 29, 2026
0d42110
fix(conformance): use the API namespace type
senamakel Aug 29, 2026
e676749
style(conformance): reorder namespace import
senamakel Aug 29, 2026
2ea2a76
chore(deps): remove tinymemory-bus dependency
senamakel Aug 29, 2026
5b32c82
docs(memory): document logical namespace storage separation
senamakel Aug 29, 2026
c1a3098
docs(memory): document sectioned write conformance
senamakel Aug 29, 2026
ca5db51
Merge remote-tracking branch 'upstream/main' into logical-namespace-r…
senamakel Aug 29, 2026
d535ffb
docs(memory): update section API documentation
senamakel Aug 29, 2026
0a76f4a
chore(safety): update safety module
senamakel Aug 29, 2026
55aea49
chore(core): update store safety module
senamakel Aug 29, 2026
587b6d0
chore: update namespace store documents
senamakel Aug 29, 2026
eac6895
chore: update namespace document store
senamakel Aug 29, 2026
bd1cc54
chore(namespace-store): update documents implementation
senamakel Aug 29, 2026
0b1059d
refactor(core): update memory store trait definitions
senamakel Aug 29, 2026
183d2c1
test(store): cover namespace summary normalization edge cases
senamakel Aug 29, 2026
f6934c8
test(core): update memory trait tests
senamakel Aug 29, 2026
f554b42
fix(safety): preserve namespace address equivalence
senamakel Aug 29, 2026
8d1ed03
test(store): verify reported namespaces remain addressable
senamakel Aug 29, 2026
eb9e987
style(tests): format namespace listing call consistently
senamakel Aug 29, 2026
628fcd7
test(memory): clarify PII namespace bracket substitution test
senamakel Aug 29, 2026
144e974
docs(specs): clarify namespace preservation assertion
senamakel Aug 29, 2026
0c89fe8
chore(core): update memory store trait
senamakel Aug 29, 2026
65680bc
chore: update memory store trait
senamakel Aug 29, 2026
46ae0cc
chore(core): update memory store trait
senamakel Aug 29, 2026
92c8e98
chore(core): update memory store trait
senamakel Aug 29, 2026
de53632
chore(core): update memory store trait
senamakel Aug 29, 2026
9b8c130
chore: update memory trait
senamakel Aug 29, 2026
74f332c
test(core): update memory trait tests
senamakel Aug 29, 2026
7e485b2
test(core): update memory trait tests
senamakel Aug 29, 2026
bdef1e7
style(core): format canonical namespace calls
senamakel Aug 29, 2026
bd99b18
docs(memory): update section API documentation
senamakel Aug 29, 2026
3802053
docs(memory): update memory section API
senamakel Aug 29, 2026
819f7ec
fix(store): restrict legacy namespace matches to exact addresses
senamakel Aug 29, 2026
4b982f9
refactor(store): reuse logical namespace filter
senamakel Aug 29, 2026
cbfa066
fix(safety): filter reads by logical namespace
senamakel Aug 29, 2026
dced0b3
refactor(store): organize safety filter import
senamakel Aug 29, 2026
bc9d25e
fix(namespace): filter scoped loads by logical namespace
senamakel Aug 29, 2026
85f6e9c
fix(namespace): filter recall by logical namespace
senamakel Aug 29, 2026
b7d10b2
test(memory): cover namespace alias isolation in recall and list
senamakel Aug 29, 2026
12263ff
fix(safety): include all legacy rows in namespace filtering
senamakel Aug 29, 2026
3f35013
fix(safety): constrain legacy rows to matching namespaces
senamakel Aug 29, 2026
3d56920
fix(namespace-query): load all documents within the namespace scope
senamakel Aug 29, 2026
eabc909
fix(namespace): filter queries by canonical logical namespace
senamakel Aug 29, 2026
897a448
docs(memory): clarify legacy namespace filtering
senamakel Aug 29, 2026
8b96112
docs: clarify memory namespace isolation behavior
senamakel Aug 29, 2026
6aa5094
fix(namespace-store): preserve logical namespace during address deriv…
senamakel Aug 29, 2026
785563a
fix(namespace): preserve logical namespace during queries
senamakel Aug 29, 2026
db5509d
refactor(query): pass namespace directly to scope loaders
senamakel Aug 29, 2026
b9e75c5
fix(store): require derived namespace address forms
senamakel Aug 29, 2026
018f553
fix(namespace-store): derive address forms for excluded queries
senamakel Aug 29, 2026
9ebf040
fix(namespace): preserve logical names in hybrid queries
senamakel Aug 29, 2026
da6491b
fix(core): normalize namespace forms for memory queries
senamakel Aug 29, 2026
ad37240
fix(retrieval): normalize namespace query addresses
senamakel Aug 29, 2026
fd6393a
fix(tinycortex): derive namespace query address forms together
senamakel Aug 29, 2026
38861fc
refactor(namespace): simplify namespace string conversion
senamakel Aug 29, 2026
576a1a9
chore(namespace): remove unused safety and namespace imports
senamakel Aug 29, 2026
1e82a18
test(namespace-store): update query tests for namespace parameter
senamakel Aug 29, 2026
08ca5cb
fix(namespace-store): pass namespace by value to event search
senamakel Aug 29, 2026
97b1ca7
style(namespace-store): format query code with rustfmt
senamakel Aug 29, 2026
d36aa6e
test(namespace-store): cover sectioned namespace context queries
senamakel Aug 29, 2026
8ea6f62
fix(namespace): sanitize namespace before querying
senamakel Aug 29, 2026
806ed02
fix(namespace): avoid double namespace sanitization
senamakel Aug 29, 2026
f33a97e
docs(core): fix namespace sanitization reference
senamakel Aug 29, 2026
7d0f14e
docs(memory): document namespace form derivation safeguards
senamakel Aug 29, 2026
f79650a
docs: document sectioned namespace query behavior
senamakel Aug 29, 2026
96990a3
fix(namespace-store): isolate query-less recall by logical namespace
senamakel Aug 29, 2026
0e63616
chore(store): restrict physical document loading to tests
senamakel Aug 29, 2026
e54593f
fix(core): scope namespace clearing across logical aliases
senamakel Aug 29, 2026
ac519e8
fix(memory): use physical namespaces for memory lookups
senamakel Aug 29, 2026
7d08251
chore: fix(store
senamakel Aug 29, 2026
1fc7528
chore(core): remove unused namespace filter import
senamakel Aug 29, 2026
89bf773
chore: files changed crates/tinymemory-core/src/store/namespace_store…
senamakel Aug 29, 2026
3048e73
fix(store): clear namespaces by physical address
senamakel Aug 29, 2026
18eba1f
chore: files changed crates/tinymemory-core/src/store/namespace_store…
senamakel Aug 29, 2026
2aa7dc8
fix(query): pass namespace by reference to event search
senamakel Aug 29, 2026
5a3c675
fix(namespace): use sanitized namespace for hybrid queries
senamakel Aug 29, 2026
faf9668
fix(namespace): align recent recall with scoped storage
senamakel Aug 29, 2026
8541f92
fix(retrieval): pass namespace directly to hit queries
senamakel Aug 29, 2026
5bb5d76
fix(tinycortex): query namespace hits with the original namespace
senamakel Aug 29, 2026
4d4084f
refactor(namespace): remove obsolete address forms helper
senamakel Aug 29, 2026
90e7521
chore(safety): remove obsolete logical namespace filter
senamakel Aug 29, 2026
f44a4ee
test(namespace-store): update query tests for simplified API
senamakel Aug 29, 2026
468a37d
test(namespace-store): update session exclusion query call
senamakel Aug 29, 2026
7d5fd4c
test(store): expect aliased namespaces to merge
senamakel Aug 29, 2026
dd392e7
docs(memory): clarify logical namespace behavior
senamakel Aug 29, 2026
dc2a0fc
docs(spec): clarify colliding namespace behavior
senamakel Aug 29, 2026
1875e7a
test: clarify double-sanitization regression rationale
senamakel Aug 29, 2026
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
3 changes: 2 additions & 1 deletion crates/tinymemory-conformance/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ pub use reference::{InMemoryProvider, REFERENCE_DRIVER_ID};
pub use suite::{
assert_awkward_content_round_trips, assert_capability_audit, assert_export_cursor_terminates,
assert_export_import_round_trip, assert_forget_is_idempotent, assert_kv_round_trip,
assert_list_filters_narrow, assert_namespaces_are_isolated, assert_provider,
assert_list_filters_narrow, assert_namespaces_are_isolated,
assert_namespaces_preserve_their_section, assert_provider,
assert_recall_respects_limit_and_namespace, assert_store_get_round_trip,
assert_taint_is_preserved, assert_upsert_replaces_rather_than_duplicates,
};
Expand Down
66 changes: 66 additions & 0 deletions crates/tinymemory-conformance/src/suite/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use std::sync::Arc;

use tinymemory_api::capabilities::Capability;
use tinymemory_api::error::MemoryError;
use tinymemory_api::namespace::Namespace;
use tinymemory_api::provider::{audit_provider, ExportRecord, MemoryProvider, SourceScope};
use tinymemory_api::recall::OwnedRecallOpts;
use tinymemory_api::types::{MemoryCategory, MemoryTaint};
Expand Down Expand Up @@ -61,6 +62,7 @@ pub async fn assert_provider(provider: Arc<dyn MemoryProvider>) {
assert_list_filters_narrow(p).await;
assert_taint_is_preserved(p).await;
assert_recall_respects_limit_and_namespace(p).await;
assert_namespaces_preserve_their_section(p).await;
assert_recall_respects_source_scope(p).await;
assert_export_import_round_trip(p).await;
assert_awkward_content_round_trips(p).await;
Expand Down Expand Up @@ -489,6 +491,70 @@ pub async fn assert_recall_respects_limit_and_namespace(provider: &dyn MemoryPro
cleanup(provider, &theirs, &["other"]).await;
}

/// `namespaces()` reports a sectioned namespace back under the same
/// [`tinymemory_api::namespace::MemorySection`] the caller wrote it in.
///
/// This is the regression the unified SQLite store's own storage-address
/// sanitiser taught us to check for: a driver whose on-disk address collapses
/// `:` to `_` (a real filesystem constraint) must still report the *logical*
/// namespace back through `namespaces()`, or `conversation:thread-8f21`
/// silently re-addresses out of the `conversation` section and every caller
/// enumerating a section's scopes sees nothing, even though the write itself
/// succeeded.
///
/// # Panics
///
/// Panics when no reported namespace parses to the same section as the one
/// that was written.
pub async fn assert_namespaces_preserve_their_section(provider: &dyn MemoryProvider) {
let who = provider.driver_id();
let namespace = format!("conversation:{}", ns(provider, "section-thread"));
let written = Namespace::parse(&namespace)
.unwrap_or_else(|e| panic!("{who}: test fixture `{namespace}` failed to parse: {e}"));

provider
.store(
&namespace,
"k",
"content",
MemoryCategory::Core,
None,
MemoryTaint::Internal,
)
.await
.unwrap_or_else(|e| panic!("{who}: store failed: {e}"));

let summaries = provider
.namespaces()
.await
.unwrap_or_else(|e| panic!("{who}: namespaces() failed: {e}"));

let matching_scope = summaries.iter().find_map(|summary| {
Namespace::parse(&summary.namespace)
.ok()
.filter(|parsed| parsed.scope() == written.scope())
});

match matching_scope {
Some(parsed) => assert_eq!(
parsed.section(),
written.section(),
"{who}: wrote `{namespace}` under section {:?}, but namespaces() reported \
its scope back under section {:?} instead — a driver must not silently \
re-address a sectioned namespace out of its section",
written.section(),
parsed.section(),
),
None => panic!(
"{who}: namespaces() did not report any namespace with scope `{}` after \
storing `{namespace}`; got {summaries:?}",
written.scope()
),
}

cleanup(provider, &namespace, &["k"]).await;
}

/// A present, empty source scope fails closed.
///
/// # Panics
Expand Down
159 changes: 126 additions & 33 deletions crates/tinymemory-core/src/store/memory_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use rusqlite::{params, Connection, OptionalExtension};
use serde_json::json;

use crate::store::namespace_store::fts5;
use crate::store::safety::LOGICAL_NAMESPACE_FILTER_SQL;
use crate::store::types::{NamespaceDocumentInput, GLOBAL_NAMESPACE};
use crate::traits::{
Memory, MemoryCategory, MemoryEntry, MemoryTaint, NamespaceSummary, RecallOpts,
Expand Down Expand Up @@ -300,13 +301,24 @@ impl UnifiedMemory {
// be `'static` and cannot borrow the store.

/// One `memory_docs` row as `get` selects it:
/// `(document_id, key, content, updated_at, category, taint, session_id)`.
type MemoryDocRow = (String, String, String, f64, String, String, Option<String>);
/// `(document_id, key, content, updated_at, category, taint, session_id,
/// logical_namespace)`.
type MemoryDocRow = (
String,
String,
String,
f64,
String,
String,
Option<String>,
Option<String>,
);

impl UnifiedMemory {
fn get_blocking(
conn: &Arc<Mutex<Connection>>,
ns: &str,
logical: &str,
key: &str,
) -> anyhow::Result<Option<MemoryEntry>> {
let conn = conn.lock();
Expand All @@ -315,11 +327,18 @@ impl UnifiedMemory {
// readers disagree about one record. The contract's round-trip
// assertion catches exactly that (`tinymemory_conformance`), and it was
// invisible until #18 §A3 let this store be bound as a driver at all.
//
// `logical_namespace` is selected too so the returned `MemoryEntry`
// reports the row's own logical name rather than the physical address
// this method happens to have been called with — see `list_blocking`'s
// doc comment for why that distinction matters.
let row: Option<MemoryDocRow> = conn
.query_row(
"SELECT document_id, key, content, updated_at, category, taint, session_id
FROM memory_docs WHERE namespace = ?1 AND key = ?2 LIMIT 1",
params![ns, key],
&format!(
"SELECT document_id, key, content, updated_at, category, taint, session_id, logical_namespace
FROM memory_docs WHERE namespace = ?1 AND key = ?3 AND {LOGICAL_NAMESPACE_FILTER_SQL} LIMIT 1"
),
params![ns, logical, key],
|row| {
Ok((
row.get(0)?,
Expand All @@ -329,43 +348,60 @@ impl UnifiedMemory {
row.get(4)?,
row.get(5)?,
row.get(6)?,
row.get(7)?,
))
},
)
.optional()?;
Ok(row.map(
|(id, key, content, updated_at, category, taint_str, session_id)| MemoryEntry {
id,
key,
content,
namespace: Some(ns.to_string()),
category: memory_category_from_stored(&category),
timestamp: timestamp_to_rfc3339(updated_at),
session_id,
score: None,
taint: crate::MemoryTaint::from_db_str(&taint_str),
|(id, key, content, updated_at, category, taint_str, session_id, row_logical)| {
MemoryEntry {
id,
key,
content,
namespace: Some(row_logical.unwrap_or_else(|| ns.to_string())),
category: memory_category_from_stored(&category),
timestamp: timestamp_to_rfc3339(updated_at),
session_id,
score: None,
taint: crate::MemoryTaint::from_db_str(&taint_str),
}
},
))
}

/// List every row addressed to one namespace, physical **and** logical.
///
/// A caller lists `learning:rust`; `ns` is the sanitized `learning_rust`
/// storage address, and `logical` is `learning:rust` itself. Filtering on
/// physical address alone would also return `learning_rust`'s own rows
/// (a distinct logical namespace that happens to sanitize identically),
/// mislabelling them as belonging to the section that was listed —
/// exactly the incompleteness `logical_namespace` exists to close. Each
/// returned entry's `namespace` is the row's *own* logical name (falling
/// back to the physical address only for pre-migration NULL rows), never
/// the caller's query namespace, so a row that genuinely came from the
/// aliased NULL-logical legacy address is still labelled honestly.
fn list_blocking(
conn: &Arc<Mutex<Connection>>,
ns: &str,
logical: &str,
category: Option<&MemoryCategory>,
session_id: Option<&str>,
) -> anyhow::Result<Vec<MemoryEntry>> {
let conn = conn.lock();
let mut stmt = conn.prepare(
"SELECT document_id, key, content, category, session_id, updated_at, taint
FROM memory_docs WHERE namespace = ?1 ORDER BY updated_at DESC",
)?;
let rows = stmt.query_map(params![ns], |row| {
let mut stmt = conn.prepare(&format!(
"SELECT document_id, key, content, category, session_id, updated_at, taint, logical_namespace
FROM memory_docs WHERE namespace = ?1 AND {LOGICAL_NAMESPACE_FILTER_SQL} ORDER BY updated_at DESC"
))?;
let rows = stmt.query_map(params![ns, logical], |row| {
let stored_category: String = row.get(3)?;
let row_logical: Option<String> = row.get(7)?;
Ok(MemoryEntry {
id: row.get(0)?,
key: row.get(1)?,
content: row.get(2)?,
namespace: Some(ns.to_string()),
namespace: Some(row_logical.unwrap_or_else(|| ns.to_string())),
category: memory_category_from_stored(&stored_category),
session_id: row.get(4)?,
timestamp: timestamp_to_rfc3339(row.get(5)?),
Expand All @@ -386,13 +422,16 @@ impl UnifiedMemory {
fn forget_lookup_blocking(
conn: &Arc<Mutex<Connection>>,
ns: &str,
logical: &str,
key: &str,
) -> anyhow::Result<Option<String>> {
let conn = conn.lock();
Ok(conn
.query_row(
"SELECT document_id FROM memory_docs WHERE namespace = ?1 AND key = ?2 LIMIT 1",
params![ns, key],
&format!(
"SELECT document_id FROM memory_docs WHERE namespace = ?1 AND key = ?3 AND {LOGICAL_NAMESPACE_FILTER_SQL} LIMIT 1"
),
params![ns, logical, key],
|row| row.get(0),
)
.optional()?)
Expand All @@ -402,11 +441,43 @@ impl UnifiedMemory {
conn: &Arc<Mutex<Connection>>,
) -> anyhow::Result<Vec<NamespaceSummary>> {
let conn = conn.lock();
// `COALESCE(logical_namespace, namespace)` is the entire backfill
// story, deliberately: rows written before the `logical_namespace`
// column existed have it NULL and fall back to exactly today's
// sanitized value. A sanitized `_` cannot be reconstructed into
// whatever delimiter it replaced (a scope may legitimately contain
// `_`), so guessing would silently mislabel unrelated namespaces —
// NULL rows simply keep reporting their sanitized address.
//
// `GROUP BY COALESCE(logical_namespace, namespace)` — the logical
// name, not the raw storage address — so two distinct logical names
// that happen to sanitize to the same physical address
// (`conversation:x` and `conversation_x` both sanitize to
// `conversation_x`) get two separate summaries with their own counts.
//
// This used to group by `namespace` (the address) instead, on the
// reasoning that every addressed call already merged aliased rows
// into one physical namespace, so grouping by logical name would
// split one merged namespace's rows across two summaries with two
// partial counts. That reasoning no longer holds: `list`/`get`/
// `forget` now filter on `logical_namespace` too (see
// `LOGICAL_NAMESPACE_FILTER_SQL`), so an addressed call for one
// logical name only ever returns that name's own rows. Grouping
// summaries by address here, while reads are scoped by logical name,
// would report one summary for both aliases while `list` on that
// reported name only ever returns half its count — and would hide
// the other alias from enumeration entirely, exactly the leak this
// fixes.
//
// Legacy rows with `logical_namespace IS NULL` still group by their
// physical address (`COALESCE` falls through to `namespace`), which
// matches what `list`'s `OR logical_namespace IS NULL` arm returns
// for that address.
let mut stmt = conn.prepare(
"SELECT namespace, COUNT(*) AS n, MAX(updated_at) AS last
"SELECT COALESCE(logical_namespace, namespace) AS ns, COUNT(*) AS n, MAX(updated_at) AS last
FROM memory_docs
GROUP BY namespace
ORDER BY namespace",
GROUP BY COALESCE(logical_namespace, namespace)
Comment thread
senamakel marked this conversation as resolved.
Outdated
ORDER BY ns",
)?;
let rows = stmt.query_map([], |row| {
let ns: String = row.get(0)?;
Expand Down Expand Up @@ -529,8 +600,9 @@ impl Memory for UnifiedMemory {
limit: usize,
min_vector_similarity: f64,
) -> anyhow::Result<Vec<(String, String)>> {
let (ns, logical) = UnifiedMemory::namespace_address_forms(namespace);
let hits = self
.query_namespace_hits(namespace, query, limit as u32)
.query_namespace_hits(&ns, &logical, query, limit as u32)
.await
.map_err(anyhow::Error::msg)?;
Ok(hits
Expand All @@ -548,9 +620,16 @@ impl Memory for UnifiedMemory {
// changed anything — the caller then reads the row as absent and stores
// it again, which is the retry loop behind #5164.
let ns = UnifiedMemory::sanitize_namespace(namespace);
// The same delimiter-preserving logical name the write path bound
// into `logical_namespace` (`canonical_logical_namespace`), so `get`
// addresses the row by both its physical and logical identity — see
// `LOGICAL_NAMESPACE_FILTER_SQL`'s doc comment for why the physical
// address alone is not enough.
let logical =
crate::store::safety::canonical_logical_namespace(namespace, GLOBAL_NAMESPACE);
let key = crate::store::safety::canonical_document_key(key);
let conn = Arc::clone(&self.conn);
tokio::task::spawn_blocking(move || Self::get_blocking(&conn, &ns, &key))
tokio::task::spawn_blocking(move || Self::get_blocking(&conn, &ns, &logical, &key))
.await
.context("join Memory::get")?
}
Expand All @@ -561,12 +640,21 @@ impl Memory for UnifiedMemory {
category: Option<&MemoryCategory>,
session_id: Option<&str>,
) -> anyhow::Result<Vec<MemoryEntry>> {
let ns = UnifiedMemory::sanitize_namespace(normalize_namespace(namespace));
let normalized = normalize_namespace(namespace);
let ns = UnifiedMemory::sanitize_namespace(normalized);
let logical =
crate::store::safety::canonical_logical_namespace(normalized, GLOBAL_NAMESPACE);
let category = category.cloned();
let session_id = session_id.map(str::to_owned);
let conn = Arc::clone(&self.conn);
tokio::task::spawn_blocking(move || {
Self::list_blocking(&conn, &ns, category.as_ref(), session_id.as_deref())
Self::list_blocking(
&conn,
&ns,
&logical,
category.as_ref(),
session_id.as_deref(),
)
})
.await
.context("join Memory::list")?
Expand All @@ -577,13 +665,18 @@ impl Memory for UnifiedMemory {
// addresses the raw caller identifiers can never delete a row whose
// namespace or key was canonicalized on the way in.
let ns = UnifiedMemory::sanitize_namespace(namespace);
let logical =
crate::store::safety::canonical_logical_namespace(namespace, GLOBAL_NAMESPACE);
let key = crate::store::safety::canonical_document_key(key);
let row: Option<String> = {
let conn = Arc::clone(&self.conn);
let ns = ns.clone();
tokio::task::spawn_blocking(move || Self::forget_lookup_blocking(&conn, &ns, &key))
.await
.context("join Memory::forget")??
let logical = logical.clone();
tokio::task::spawn_blocking(move || {
Self::forget_lookup_blocking(&conn, &ns, &logical, &key)
})
.await
.context("join Memory::forget")??
};
let Some(document_id) = row else {
return Ok(false);
Expand Down
Loading