@@ -584,18 +584,10 @@ impl DbServer {
584584 ) ?;
585585 Ok ( Response :: Ack )
586586 }
587- Request :: DeleteConversation { conversation_id } => {
588- // conversation_id is the table's PRIMARY KEY (a globally
589- // unique UUID; see forge_repo's database/schema.rs), so it
590- // uniquely identifies the row. forge_repo additionally
591- // filters by workspace_id as a cross-workspace guard, but the
592- // daemon derives workspace_id from ITS OWN current_dir hash,
593- // which can diverge from the client's (e.g. --directory mode
594- // canonicalizes paths on Windows). Reusing that predicate here
595- // would silently no-op the delete, so we key on the unique id.
587+ Request :: DeleteConversation { conversation_id, workspace_id } => {
596588 conn. execute (
597- "DELETE FROM conversations WHERE conversation_id = ?" ,
598- rusqlite:: params![ conversation_id. into_string( ) ] ,
589+ "DELETE FROM conversations WHERE conversation_id = ? AND workspace_id = ? " ,
590+ rusqlite:: params![ conversation_id. into_string( ) , workspace_id ] ,
599591 ) ?;
600592 Ok ( Response :: Ack )
601593 }
@@ -1143,15 +1135,10 @@ mod db_tests {
11431135 assert_eq ! ( is_compressed, 0 ) ;
11441136 }
11451137
1146- /// Delete keys on the conversation_id PRIMARY KEY alone: the row is
1147- /// removed even when its workspace_id differs from the daemon's, because
1148- /// the daemon's own current_dir hash can diverge from the client's (e.g.
1149- /// --directory canonicalization on Windows) and a workspace-filtered
1150- /// predicate would silently no-op. forge_repo's workspace guard is a
1151- /// cross-user boundary that has no meaning inside a local single-user
1152- /// daemon.
1138+ /// A delete request must not remove an identically-addressed conversation
1139+ /// from a different workspace.
11531140 #[ test]
1154- fn delete_conversation_removes_row_by_unique_id_across_workspaces ( ) {
1141+ fn delete_conversation_does_not_remove_row_from_another_workspace ( ) {
11551142 let dir = TempDir :: new ( ) . unwrap ( ) ;
11561143 let conn = rusqlite:: Connection :: open ( dir. path ( ) . join ( "test.db" ) ) . unwrap ( ) ;
11571144 create_conversations_schema ( & conn) ;
@@ -1167,18 +1154,20 @@ mod db_tests {
11671154 Response :: Ack
11681155 ) ) ;
11691156
1170- // Simulate a workspace_id mismatch (client resolved a different cwd):
1171- // the daemon must still delete the row by its unique id.
1157+ let other_workspace_id = DbServer :: workspace_id ( ) + 1 ;
11721158 conn. execute (
11731159 "UPDATE conversations SET workspace_id = ?1 WHERE conversation_id = ?2" ,
1174- rusqlite:: params![ DbServer :: workspace_id ( ) + 1 , id. into_string( ) ] ,
1160+ rusqlite:: params![ other_workspace_id , id. into_string( ) ] ,
11751161 )
11761162 . unwrap ( ) ;
11771163
11781164 assert ! ( matches!(
11791165 DbServer :: execute_with_conn(
11801166 & conn,
1181- & Request :: DeleteConversation { conversation_id: id }
1167+ & Request :: DeleteConversation {
1168+ conversation_id: id,
1169+ workspace_id: DbServer :: workspace_id( ) ,
1170+ }
11821171 )
11831172 . expect( "delete" ) ,
11841173 Response :: Ack
@@ -1187,7 +1176,7 @@ mod db_tests {
11871176 let total: i64 = conn
11881177 . query_row ( "SELECT COUNT(*) FROM conversations" , [ ] , |row| row. get ( 0 ) )
11891178 . unwrap ( ) ;
1190- assert_eq ! ( total, 0 , "row deleted despite the workspace_id mismatch " ) ;
1179+ assert_eq ! ( total, 1 , "row from the other workspace must remain " ) ;
11911180 }
11921181
11931182 /// update_parent_id mirrors forge_repo: it sets parent_id (and stamps
@@ -1258,7 +1247,10 @@ mod db_tests {
12581247
12591248 let ( response_tx, response_rx) = tokio:: sync:: oneshot:: channel ( ) ;
12601249 let mut batch = vec ! [ QueuedRequest {
1261- request: Request :: DeleteConversation { conversation_id: ConversationId :: default ( ) } ,
1250+ request: Request :: DeleteConversation {
1251+ conversation_id: ConversationId :: default ( ) ,
1252+ workspace_id: DbServer :: workspace_id( ) ,
1253+ } ,
12621254 response_tx,
12631255 } ] ;
12641256 DbServer :: flush_batch ( & mut batch, & mut conn, & db_path) . await ;
0 commit comments