mirror of
https://github.com/lukaszraczylo/claude-mnemonic.git
synced 2026-10-18 06:09:15 +00:00
* fix(db): keep archived notes out of search, lists, counts and the vector rebuild An archived note is meant to be kept but unused, yet most read paths did not filter on is_archived: full-text and LIKE search, vector-hit hydration, context injection, the paginated lists, counts and the vector rebuild all still returned it. A vector hit for an archived note also still pointed at a live embedding, because archiving never dropped it. - add notArchivedFilter/visibleScope and apply them to every read path - the paginated lists take includeArchived; the API reads ?include_archived=true, and exports ask for them so a backup is complete - archiving drops the note's vectors, unarchiving syncs them back - add //go:build fts5 to observation_update_test.go so `go vet` without tags builds (it referenced a helper from a tagged file) Adds a store test that archives one of three notes and checks every path, and that fails if the filter is removed. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> * fix(worker): drop the vectors of notes archived through bulk-status too POST /api/observations/bulk-status with action "archive" archived notes but left their vectors in the index; only the dedicated archive handler dropped them. Both paths now call dropArchivedFromVectors. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5.5 <[email protected]>
108 lines
3.6 KiB
Go
108 lines
3.6 KiB
Go
//go:build fts5
|
|
|
|
package gorm
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/lukaszraczylo/claude-mnemonic/pkg/models"
|
|
)
|
|
|
|
// An archived note is kept, but it must stay out of search, context, lists, counts and the vector rebuild, and an
|
|
// export (which asks for it) still has it.
|
|
func TestArchivedObservationsAreKeptButHiddenEverywhereExceptAnExport(t *testing.T) {
|
|
s, store, cleanup := testObservationStore(t)
|
|
defer cleanup()
|
|
ctx := context.Background()
|
|
|
|
var ids []int64
|
|
for _, title := range []string{"Zebra ledger reconciliation", "Quokka deployment checklist", "Ocelot retry budget"} {
|
|
id, _, err := s.StoreObservation(ctx, "claude-1", "proj",
|
|
&models.ParsedObservation{Type: models.ObsTypeDiscovery, Title: title, Narrative: title + " notes"}, 1, 10)
|
|
require.NoError(t, err)
|
|
ids = append(ids, id)
|
|
time.Sleep(2 * time.Millisecond)
|
|
}
|
|
archivedID := ids[0] // the zebra note
|
|
require.NoError(t, s.ArchiveObservation(ctx, archivedID, "test"))
|
|
|
|
has := func(obs []*models.Observation) bool {
|
|
for _, o := range obs {
|
|
if o.ID == archivedID {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
recent, err := s.GetRecentObservations(ctx, "proj", 10)
|
|
require.NoError(t, err)
|
|
assert.False(t, has(recent), "context injection does not see it")
|
|
|
|
strict, err := s.GetObservationsByProjectStrict(ctx, "proj", 10)
|
|
require.NoError(t, err)
|
|
assert.False(t, has(strict))
|
|
|
|
all, err := s.GetAllRecentObservations(ctx, 10)
|
|
require.NoError(t, err)
|
|
assert.False(t, has(all))
|
|
|
|
byID, err := s.GetObservationsByIDs(ctx, ids, "default", 0)
|
|
require.NoError(t, err)
|
|
assert.False(t, has(byID), "a vector hit for it is dropped when it is loaded")
|
|
assert.Len(t, byID, 2)
|
|
|
|
ordered, err := s.GetObservationsByIDsPreserveOrder(ctx, ids)
|
|
require.NoError(t, err)
|
|
assert.False(t, has(ordered))
|
|
|
|
fts, err := s.SearchObservationsFTS(ctx, "zebra ledger", "proj", 10)
|
|
require.NoError(t, err)
|
|
assert.False(t, has(fts), "full-text search does not find it")
|
|
other, err := s.SearchObservationsFTS(ctx, "quokka deployment", "proj", 10)
|
|
require.NoError(t, err)
|
|
assert.Len(t, other, 1, "and still finds the others")
|
|
|
|
n, err := s.GetObservationCount(ctx, "proj")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 2, n, "the count is of notes that can be used")
|
|
|
|
rebuild, err := s.GetAllObservations(ctx)
|
|
require.NoError(t, err)
|
|
assert.False(t, has(rebuild), "a vector rebuild does not put it back into the index")
|
|
|
|
listed, total, err := s.GetAllRecentObservationsPaginated(ctx, 10, 0, false)
|
|
require.NoError(t, err)
|
|
assert.False(t, has(listed))
|
|
assert.EqualValues(t, 2, total)
|
|
projListed, projTotal, err := s.GetObservationsByProjectStrictPaginated(ctx, "proj", 10, 0, false)
|
|
require.NoError(t, err)
|
|
assert.False(t, has(projListed))
|
|
assert.EqualValues(t, 2, projTotal)
|
|
|
|
exported, total, err := s.GetAllRecentObservationsPaginated(ctx, 10, 0, true)
|
|
require.NoError(t, err)
|
|
assert.True(t, has(exported), "an export asks for them")
|
|
assert.EqualValues(t, 3, total)
|
|
projExported, _, err := s.GetObservationsByProjectStrictPaginated(ctx, "proj", 10, 0, true)
|
|
require.NoError(t, err)
|
|
assert.True(t, has(projExported))
|
|
|
|
// Kept: the row is still there, and restoring it makes it visible again.
|
|
one, err := s.GetObservationByID(ctx, archivedID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, one)
|
|
require.NoError(t, s.UnarchiveObservation(ctx, archivedID))
|
|
back, err := s.SearchObservationsFTS(ctx, "zebra ledger", "proj", 10)
|
|
require.NoError(t, err)
|
|
assert.True(t, has(back))
|
|
var rows int64
|
|
require.NoError(t, store.DB.Model(&Observation{}).Where("project = ?", "proj").Count(&rows).Error)
|
|
assert.EqualValues(t, 3, rows)
|
|
}
|