fix(vector): re-sync a note's vectors by replacing them; roll back a failed insert (#68)

The vectors table is a vec0 virtual table, where INSERT OR REPLACE does not replace, so syncing a note that already had vectors failed with a UNIQUE constraint error and the edit never reached the index. AddDocuments also shadowed err inside its loop, so a failed insert left the write transaction open and later writes failed with 'database is locked'.

Add Client.ReplaceDocuments (delete and insert in one transaction) and use it from SyncObservation and SyncSummary. The shared write function has a named result, so every failed return rolls back. Fixes #66.

Co-authored-by: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
Holger Reinhardt
2026-10-04 23:15:46 +01:00
committed by GitHub
co-authored by Claude Sonnet 5.5
parent eba3136fff
commit 67de0f0c84
4 changed files with 158 additions and 25 deletions
+3 -10
View File
@@ -22,11 +22,8 @@ func NewSync(client *Client) *Sync {
// SyncObservation syncs a single observation to the vector store.
func (s *Sync) SyncObservation(ctx context.Context, obs *models.Observation) error {
docs := s.formatObservationDocs(obs)
if len(docs) == 0 {
return nil
}
if err := s.client.AddDocuments(ctx, docs); err != nil {
// Replace, not add: a note that is synced again (after an edit, a re-scope or a re-import) already has documents.
if err := s.client.ReplaceDocuments(ctx, "observation", obs.ID, docs); err != nil {
return fmt.Errorf("add observation docs: %w", err)
}
@@ -102,11 +99,7 @@ func (s *Sync) formatObservationDocs(obs *models.Observation) []Document {
// SyncSummary syncs a single session summary to the vector store.
func (s *Sync) SyncSummary(ctx context.Context, summary *models.SessionSummary) error {
docs := s.formatSummaryDocs(summary)
if len(docs) == 0 {
return nil
}
if err := s.client.AddDocuments(ctx, docs); err != nil {
if err := s.client.ReplaceDocuments(ctx, "session_summary", summary.ID, docs); err != nil {
return fmt.Errorf("add summary docs: %w", err)
}