Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

### Changed

- Internal: `memories.author`, part 1 of #1427 phase 2 (schema v22): an optional, free-text author on the memory itself, usable without a room. The first open by a build with this change migrates the store in place (additive `ALTER TABLE`; a memory that belongs to exactly one room takes that room link's author, the rest stay unset). `Memory` gains `author` (omitted from JSON when unset) and `Uteke::set_author` validates it (trimmed, 1 to 128 characters, no control characters). No HTTP, MCP or CLI input accepts it yet: that follows once uteke-cloud#219 is answered.
- Internal: privacy filter detection module, part 1 of #1464 (`uteke_core::privacy`): `redact` masks AWS access keys, GitHub tokens, `sk-` API keys, bearer headers, JWTs, PEM private key blocks and long secret-named assignments (`API_KEY=...`, with an entropy check and exceptions for hashes, UUIDs, paths and placeholders), keeping the shape (`ghp_[REDACTED:github-token]`), with an allowlist for false positives. Nothing calls it yet: it is wired into the write paths, behind `[privacy] mode`, in part 2, after the cloud contract answer (uteke-cloud#218).
- Internal: scope-isolation regression gate, parts 1 and 2 (#1467): the HTTP suite runs 20 scoped read requests through `route` (every recall strategy, search, list, recent, export, graph, room, orphans, tags, stats, doc) and a test fails when a read route is added to the registry without an isolation check or a documented exemption. Part 1: a two-namespace fixture with overlapping vocabulary and a core suite asserting that `recall` (every strategy), unified recall, `search`, `list`, tags, export, room recall and graph views never return a record from another namespace. The fixture ships behind the `test-support` feature of `uteke-core` for the server, MCP and CLI suites that follow.
- Internal: scope-isolation regression gate, part 3 (#1467): the MCP suite over the read-only tools (a test fails when a tool is added without an isolation check or an exemption), a CLI suite that runs the binary against a two-namespace store, and `docs/isolation-suite.md`, which lists what each transport covers and the exemptions.
Expand Down
1 change: 1 addition & 0 deletions crates/uteke-core/src/consolidate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,7 @@ mod tests {
source: None,
source_type: "direct".to_string(),
author_type: "agent".to_string(),
author: None,
id: String::new(),
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/uteke-core/src/consolidation_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ mod tests {
source: None,
source_type: "unknown".to_string(),
author_type: "agent".to_string(),
author: None,
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/uteke-core/src/consolidation_exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ pub fn execute_plan<U: ConsolidationStore>(
source: Some(format!("room:{room_id}#consolidation")),
source_type: "derived".to_string(),
author_type: "agent".to_string(),
author: None,
};

let source_refs: Vec<&Memory> = sources.iter().collect();
Expand Down
1 change: 1 addition & 0 deletions crates/uteke-core/src/consolidation_plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ mod tests {
source: None,
source_type: String::new(),
author_type: String::new(),
author: None,
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/uteke-core/src/edges.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,7 @@ mod tests {
source: None,
source_type: "user".to_string(),
author_type: "agent".to_string(),
author: None,
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/uteke-core/src/forget_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ mod tests {
source: None,
source_type: "user".to_string(),
author_type: "agent".to_string(),
author: None,
};
uteke.store().insert(&m).unwrap();
}
Expand Down
1 change: 1 addition & 0 deletions crates/uteke-core/src/graph_rerank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ mod tests {
source: None,
source_type: "user".to_string(),
author_type: "agent".to_string(),
author: None,
},
score,
}
Expand Down
1 change: 1 addition & 0 deletions crates/uteke-core/src/import_export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ impl crate::Uteke {
source: Some(format!("import:{}", entry.source.unwrap_or_default())),
source_type: "import".to_string(),
author_type: "agent".to_string(),
author: None,
};

// Write-ahead: vector index first (can be rolled back), then SQLite.
Expand Down
10 changes: 10 additions & 0 deletions crates/uteke-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1437,6 +1437,13 @@ impl Uteke {
self.store.set_author_type(id, author_type)
}

/// Set or clear who wrote a memory (#1427 phase 2): free text, trimmed,
/// 1 to 128 characters, no control characters. Works with or without a
/// room. Returns false if the memory does not exist.
pub fn set_author(&self, id: &str, author: Option<&str>) -> Result<bool, Error> {
self.store.set_author(id, author)
}

/// Recalculate importance scores for all memories.
pub fn recompute_importance(&self) -> Result<usize, Error> {
let updated = self.store.recompute_importance()?;
Expand Down Expand Up @@ -3292,6 +3299,7 @@ mod tests {
source: None,
source_type: "user".to_string(),
author_type: "agent".to_string(),
author: None,
};

let json = serde_json::to_string(&m).unwrap();
Expand Down Expand Up @@ -3343,6 +3351,7 @@ mod tests {
source: None,
source_type: "user".to_string(),
author_type: "agent".to_string(),
author: None,
};

let sr = SearchResult {
Expand Down Expand Up @@ -4145,6 +4154,7 @@ mod tests {
source: None,
source_type: "user".to_string(),
author_type: "agent".to_string(),
author: None,
};
uteke.store.insert(&m1).unwrap();

Expand Down
8 changes: 6 additions & 2 deletions crates/uteke-core/src/memory/crud.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ impl super::Store {
) -> Result<(), Error> {
self.conn
.execute(
"INSERT INTO memories (id, content, embedding, tags, metadata, created_at, updated_at, namespace, access_count, last_accessed, deprecated, valid_from, valid_until, memory_type, importance, pinned, content_type, slug, source, source_type, author_type, deprecated_at, recall_count)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23)",
"INSERT INTO memories (id, content, embedding, tags, metadata, created_at, updated_at, namespace, access_count, last_accessed, deprecated, valid_from, valid_until, memory_type, importance, pinned, content_type, slug, source, source_type, author_type, deprecated_at, recall_count, author)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24)",
params![
memory.id,
memory.content,
Expand All @@ -129,6 +129,7 @@ impl super::Store {
memory.author_type,
memory.deprecated_at.map(|t| t.to_rfc3339()),
memory.recall_count,
memory.author,
],
)
.map_err(|e| Error::db("Failed to insert memory", e))?;
Expand Down Expand Up @@ -1090,6 +1091,7 @@ mod content_type_tests {
source: None,
source_type: "user".to_string(),
author_type: "agent".to_string(),
author: None,
};
store.insert(&memory).unwrap();

Expand Down Expand Up @@ -1125,6 +1127,7 @@ mod content_type_tests {
source: None,
source_type: "user".to_string(),
author_type: "agent".to_string(),
author: None,
};
store.insert(&memory).unwrap();

Expand Down Expand Up @@ -1175,6 +1178,7 @@ mod content_type_tests {
source: None,
source_type: "user".to_string(),
author_type: "agent".to_string(),
author: None,
};
store.insert(&m1).unwrap();

Expand Down
1 change: 1 addition & 0 deletions crates/uteke-core/src/memory/fts5.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ mod tests {
source: None,
source_type: "user".to_string(),
author_type: "agent".to_string(),
author: None,
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/uteke-core/src/memory/rooms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1243,6 +1243,7 @@ mod tests {
source: None,
source_type: "user".to_string(),
author_type: "agent".to_string(),
author: None,
}
}

Expand Down
31 changes: 31 additions & 0 deletions crates/uteke-core/src/memory/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,9 @@ impl super::Store {
// v21: recall_count column — recall/search hits are counted apart
// from explicit gets (#1337)
21 => self.migrate_v20_to_v21()?,
// v22: memories.author — who wrote it, with or without a room
// (#1427 phase 2)
22 => self.migrate_v21_to_v22()?,
_ => {
// No-op for future versions.
}
Expand Down Expand Up @@ -1134,6 +1137,34 @@ impl super::Store {
Ok(())
}

/// v22: add `author` (#1427 phase 2), the agent or user that wrote a memory,
/// usable without a room. Nullable and additive. A memory that belongs to
/// exactly one room takes the author recorded on that room link; the rest
/// stay NULL (unknown), never guessed.
fn migrate_v21_to_v22(&self) -> Result<(), Error> {
tracing::info!("Applying schema migration v21 to v22: author column");
if !self.column_exists("author") {
self.conn
.execute_batch("ALTER TABLE memories ADD COLUMN author TEXT;")
.map_err(|e| Error::db("schema migration v21 to v22", e))?;
}
let backfilled = self
.conn
.execute(
"UPDATE memories SET author = ( \
SELECT rm.author FROM room_memories rm WHERE rm.memory_id = memories.id \
) \
WHERE author IS NULL \
AND (SELECT COUNT(*) FROM room_memories rm WHERE rm.memory_id = memories.id) = 1",
[],
)
.map_err(|e| Error::db("backfill memories.author", e))?;
tracing::info!(
"Migration v21 to v22 complete: author column added, {backfilled} backfilled"
);
Ok(())
}

/// v16: Add author_type column (human|agent) to memories (#1083).
///
/// Distinguishes memories authored by a human (direct user capture) from
Expand Down
Loading
Loading