@@ -971,20 +971,38 @@ impl GlobalDb {
971971 /// in-process and retried briefly to also cover a racing *external*
972972 /// process (e.g. two MCP servers starting simultaneously).
973973 pub async fn open_at ( db_path : & std:: path:: Path ) -> Option < Self > {
974+ Self :: open_at_with_backfill ( db_path, true ) . await
975+ }
976+
977+ /// Opens and ensures a writable session store without starting detached
978+ /// structured backfill. Bulk multi-store catch-up uses this to avoid
979+ /// launching one competing backfill task per registered project.
980+ pub async fn open_at_without_structured_backfill ( db_path : & std:: path:: Path ) -> Option < Self > {
981+ Self :: open_at_with_backfill ( db_path, false ) . await
982+ }
983+
984+ async fn open_at_with_backfill (
985+ db_path : & std:: path:: Path ,
986+ spawn_structured_backfill : bool ,
987+ ) -> Option < Self > {
974988 static OPEN_ENSURE_LOCK : tokio:: sync:: Mutex < ( ) > = tokio:: sync:: Mutex :: const_new ( ( ) ) ;
975989 let _guard = OPEN_ENSURE_LOCK . lock ( ) . await ;
976990 for attempt in 0 ..3_u64 {
977991 if attempt > 0 {
978992 tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 50 * attempt) ) . await ;
979993 }
980- if let Some ( db) = Self :: open_at_unsynchronized ( db_path) . await {
994+ if let Some ( db) = Self :: open_at_unsynchronized ( db_path, spawn_structured_backfill) . await
995+ {
981996 return Some ( db) ;
982997 }
983998 }
984999 None
9851000 }
9861001
987- async fn open_at_unsynchronized ( db_path : & std:: path:: Path ) -> Option < Self > {
1002+ async fn open_at_unsynchronized (
1003+ db_path : & std:: path:: Path ,
1004+ spawn_structured_backfill : bool ,
1005+ ) -> Option < Self > {
9881006 if let Some ( parent) = db_path. parent ( ) {
9891007 std:: fs:: create_dir_all ( parent) . ok ( ) ?;
9901008 }
@@ -1204,7 +1222,9 @@ impl GlobalDb {
12041222 // runs on every open (per hook event, per CLI/MCP invocation), so it
12051223 // must not block: schedule it on a detached background task rather than
12061224 // synchronously reading and re-parsing a batch of multi-MB transcripts.
1207- db. spawn_structured_backfill ( ) ;
1225+ if spawn_structured_backfill {
1226+ db. spawn_structured_backfill ( ) ;
1227+ }
12081228
12091229 Some ( db)
12101230 }
@@ -3477,10 +3497,17 @@ impl GlobalDb {
34773497 return false ;
34783498 }
34793499 }
3480- if !self
3481- . set_parse_offset_in_existing_tx ( parse_offset_path, parse_offset)
3482- . await
3483- {
3500+ let cursor_set = match mode {
3501+ TranscriptWriteMode :: Full => {
3502+ self . set_parse_offset_in_existing_tx ( parse_offset_path, parse_offset)
3503+ . await
3504+ }
3505+ TranscriptWriteMode :: ProjectionOnly => {
3506+ self . set_parse_offset_monotonic_in_existing_tx ( parse_offset_path, parse_offset)
3507+ . await
3508+ }
3509+ } ;
3510+ if !cursor_set {
34843511 let _ = self . conn . execute ( "ROLLBACK" , ( ) ) . await ;
34853512 return false ;
34863513 }
@@ -4851,6 +4878,39 @@ impl GlobalDb {
48514878 let _ = self . set_parse_offset_in_existing_tx ( path, offset) . await ;
48524879 }
48534880
4881+ /// Advances a row-style parse cursor without allowing an overlapping,
4882+ /// older sweep to move it backwards.
4883+ pub async fn advance_parse_offset ( & self , path : & str , offset : ParseOffset ) {
4884+ let _ = self
4885+ . set_parse_offset_monotonic_in_existing_tx ( path, offset)
4886+ . await ;
4887+ }
4888+
4889+ async fn set_parse_offset_monotonic_in_existing_tx (
4890+ & self ,
4891+ path : & str ,
4892+ offset : ParseOffset ,
4893+ ) -> bool {
4894+ self . conn
4895+ . execute (
4896+ "INSERT INTO parse_offsets (file_path, byte_offset, mtime, file_id)
4897+ VALUES (?1, ?2, ?3, ?4)
4898+ ON CONFLICT(file_path) DO UPDATE SET
4899+ byte_offset = excluded.byte_offset,
4900+ mtime = excluded.mtime,
4901+ file_id = excluded.file_id
4902+ WHERE excluded.byte_offset >= parse_offsets.byte_offset" ,
4903+ params ! [
4904+ path,
4905+ offset. byte_offset as i64 ,
4906+ offset. mtime as i64 ,
4907+ offset. file_id as i64
4908+ ] ,
4909+ )
4910+ . await
4911+ . is_ok ( )
4912+ }
4913+
48544914 async fn set_parse_offset_in_existing_tx ( & self , path : & str , offset : ParseOffset ) -> bool {
48554915 if self
48564916 . conn
0 commit comments