Files
kenn-io--agentsview/internal/parser/zed_shelley_provider_test.go
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

787 lines
27 KiB
Go

package parser
import (
"context"
"database/sql"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestZedProviderCapabilities(t *testing.T) {
factory, ok := ProviderFactoryByType(AgentZed)
require.True(t, ok)
require.NotNil(t, factory)
caps := factory.Capabilities()
assert.Equal(t, CapabilityUnsupported, caps.Content.Relationships)
assert.Equal(t, CapabilitySupported, caps.Content.AggregateUsageEvents)
provider, ok := NewProvider(AgentZed, ProviderConfig{
Roots: []string{t.TempDir()},
Machine: "devbox",
})
require.True(t, ok)
require.NotNil(t, provider)
}
func TestZedProviderSourceMethods(t *testing.T) {
fixture := zedProviderReadFixture(t)
root := fixture.Root
dbPath := fixture.DBPath
threadID := fixture.FirstThreadID
virtualPath := ZedSQLiteVirtualPath(dbPath, threadID)
provider, ok := NewProvider(AgentZed, ProviderConfig{
Roots: []string{root},
Machine: "devbox",
})
require.True(t, ok)
plan, err := provider.WatchPlan(context.Background())
require.NoError(t, err)
require.Len(t, plan.Roots, 1)
assert.Equal(t, filepath.Join(root, "threads"), plan.Roots[0].Path)
assert.False(t, plan.Roots[0].Recursive)
assert.Equal(t, []string{"threads.db", "threads.db-*"}, plan.Roots[0].IncludeGlobs)
discovered, err := provider.Discover(context.Background())
require.NoError(t, err)
require.Len(t, discovered, 1)
assert.Equal(t, AgentZed, discovered[0].Provider)
assert.Equal(t, dbPath, discovered[0].DisplayPath)
assert.Equal(t, dbPath, discovered[0].FingerprintKey)
found, ok, err := provider.FindSource(context.Background(), FindSourceRequest{
FullSessionID: "host~zed:" + threadID,
})
require.NoError(t, err)
require.True(t, ok)
assert.Equal(t, virtualPath, found.DisplayPath)
assert.Equal(t, virtualPath, found.FingerprintKey)
fingerprint, err := provider.Fingerprint(context.Background(), found)
require.NoError(t, err)
assert.Equal(t, virtualPath, fingerprint.Key)
assert.Positive(t, fingerprint.Size)
assert.NotZero(t, fingerprint.MTimeNS)
assert.NotEmpty(t, fingerprint.Hash)
changed, err := provider.SourcesForChangedPath(
context.Background(),
ChangedPathRequest{Path: dbPath + "-wal", EventKind: "write", WatchRoot: filepath.Dir(dbPath)},
)
require.NoError(t, err)
require.Len(t, changed, 1)
assert.Equal(t, dbPath, changed[0].DisplayPath)
}
func TestZedProviderParsePhysicalAndVirtualSources(t *testing.T) {
fixture := zedProviderReadFixture(t)
root := fixture.Root
threadOne := fixture.FirstThreadID
threadTwo := fixture.SecondThreadID
provider, ok := NewProvider(AgentZed, ProviderConfig{
Roots: []string{root},
Machine: "devbox",
})
require.True(t, ok)
sources, err := provider.Discover(context.Background())
require.NoError(t, err)
require.Len(t, sources, 1)
allOutcome, err := provider.Parse(context.Background(), ParseRequest{
Source: sources[0],
})
require.NoError(t, err)
require.True(t, allOutcome.ResultSetComplete)
require.True(t, allOutcome.ForceReplace)
require.Len(t, allOutcome.Results, 2)
assert.Equal(t, "zed:"+threadOne, allOutcome.Results[0].Result.Session.ID)
assert.Equal(t, "zed:"+threadTwo, allOutcome.Results[1].Result.Session.ID)
virtualSource, ok, err := provider.FindSource(context.Background(), FindSourceRequest{
RawSessionID: threadTwo,
})
require.NoError(t, err)
require.True(t, ok)
oneOutcome, err := provider.Parse(context.Background(), ParseRequest{
Source: virtualSource,
})
require.NoError(t, err)
require.True(t, oneOutcome.ResultSetComplete)
require.True(t, oneOutcome.ForceReplace)
require.Len(t, oneOutcome.Results, 1)
assert.Equal(t, "zed:"+threadTwo, oneOutcome.Results[0].Result.Session.ID)
assert.Equal(t, "devbox", oneOutcome.Results[0].Result.Session.Machine)
assert.Len(t, oneOutcome.Results[0].Result.Messages, 1)
}
func TestZedProviderFingerprintIncludesWALSiblings(t *testing.T) {
root := t.TempDir()
dbPath := filepath.Join(root, zedThreadsDBRelPath)
require.NoError(t, os.MkdirAll(filepath.Dir(dbPath), 0o755))
createZedThreadsDBAt(t, dbPath, []zedTestThread{{
id: "10431c84-c47b-4e6c-b2df-f9f3b9ad025b",
summary: "Provider thread",
updatedAt: "2026-06-08T09:14:10Z",
dataType: "json",
data: []byte(`{"messages":[{"User":{"content":[{"Text":"Hello Zed"}]}}]}`),
}})
provider, ok := NewProvider(AgentZed, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
sources, err := provider.Discover(context.Background())
require.NoError(t, err)
require.Len(t, sources, 1)
before, err := provider.Fingerprint(context.Background(), sources[0])
require.NoError(t, err)
walPath := dbPath + "-wal"
writeSourceFile(t, walPath, "wal")
walTime := time.Unix(0, before.MTimeNS+int64(time.Second))
require.NoError(t, os.Chtimes(walPath, walTime, walTime))
after, err := provider.Fingerprint(context.Background(), sources[0])
require.NoError(t, err)
assert.Equal(t, before.Size, after.Size)
assert.Greater(t, after.MTimeNS, before.MTimeNS)
}
func TestZedProviderClassifiesDeletedPhysicalDB(t *testing.T) {
root := t.TempDir()
dbPath := filepath.Join(root, zedThreadsDBRelPath)
require.NoError(t, os.MkdirAll(filepath.Dir(dbPath), 0o755))
createZedThreadsDBAt(t, dbPath, []zedTestThread{{
id: "10431c84-c47b-4e6c-b2df-f9f3b9ad025b",
summary: "Provider thread",
updatedAt: "2026-06-08T09:14:10Z",
dataType: "json",
data: []byte(`{"messages":[{"User":{"content":[{"Text":"Hello Zed"}]}}]}`),
}})
require.NoError(t, os.Remove(dbPath))
provider, ok := NewProvider(AgentZed, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
changed, err := provider.SourcesForChangedPath(
context.Background(),
ChangedPathRequest{Path: dbPath, EventKind: "remove", WatchRoot: filepath.Dir(dbPath)},
)
require.NoError(t, err)
require.Len(t, changed, 1)
assert.Equal(t, dbPath, changed[0].DisplayPath)
outcome, err := provider.Parse(context.Background(), ParseRequest{Source: changed[0]})
require.NoError(t, err)
assert.True(t, outcome.ResultSetComplete)
// The backing DB file is gone, so the outcome must not force-replace: the
// persistent archive preserves sessions whose source file no longer exists.
assert.False(t, outcome.ForceReplace)
assert.Equal(t, SkipNoSession, outcome.SkipReason)
assert.Empty(t, outcome.Results)
}
func TestZedProviderStoredVirtualPathFreshness(t *testing.T) {
root := t.TempDir()
dbPath := filepath.Join(root, zedThreadsDBRelPath)
threadID := "10431c84-c47b-4e6c-b2df-f9f3b9ad025b"
require.NoError(t, os.MkdirAll(filepath.Dir(dbPath), 0o755))
createZedThreadsDBAt(t, dbPath, []zedTestThread{{
id: threadID,
summary: "Provider thread",
updatedAt: "2026-06-08T09:14:10Z",
dataType: "json",
data: []byte(`{"messages":[{"User":{"content":[{"Text":"Hello Zed"}]}}]}`),
}})
virtualPath := ZedSQLiteVirtualPath(dbPath, threadID)
provider, ok := NewProvider(AgentZed, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
found, ok, err := provider.FindSource(context.Background(), FindSourceRequest{
StoredFilePath: virtualPath,
RequireFreshSource: true,
})
require.NoError(t, err)
require.True(t, ok)
assert.Equal(t, virtualPath, found.DisplayPath)
db, err := sql.Open("sqlite3", dbPath)
require.NoError(t, err)
_, err = db.Exec(`DELETE FROM threads WHERE id = ?`, threadID)
require.NoError(t, err)
require.NoError(t, db.Close())
_, ok, err = provider.FindSource(context.Background(), FindSourceRequest{
StoredFilePath: virtualPath,
RequireFreshSource: true,
})
require.NoError(t, err)
assert.False(t, ok, "fresh lookup must reject a deleted virtual row")
staleSource, ok, err := provider.FindSource(context.Background(), FindSourceRequest{
StoredFilePath: virtualPath,
})
require.NoError(t, err)
require.True(t, ok, "non-fresh lookup keeps tombstone source identity")
outcome, err := provider.Parse(context.Background(), ParseRequest{
Source: staleSource,
})
require.NoError(t, err)
assert.True(t, outcome.ResultSetComplete)
assert.True(t, outcome.ForceReplace)
assert.Equal(t, SkipNoSession, outcome.SkipReason)
assert.Empty(t, outcome.Results)
}
// TestZedProviderChangedPathTombstonesDeletedThread verifies the changed-path
// classifier emits a tombstone for a stored Zed thread deleted from a
// still-present database, so the engine force-replaces it out of the archive.
// The surviving thread is left to the whole-DB fan-out; a vanished database
// emits no tombstone (stored sessions preserved per the persistent-archive
// rule).
func TestZedProviderChangedPathTombstonesDeletedThread(t *testing.T) {
root := t.TempDir()
dbPath := filepath.Join(root, zedThreadsDBRelPath)
threadsDir := filepath.Join(root, "threads")
survivingID := "10431c84-c47b-4e6c-b2df-f9f3b9ad025b"
deletedID := "20431c84-c47b-4e6c-b2df-f9f3b9ad025b"
require.NoError(t, os.MkdirAll(filepath.Dir(dbPath), 0o755))
createZedThreadsDBAt(t, dbPath, []zedTestThread{
{
id: survivingID, summary: "Surviving thread",
updatedAt: "2026-06-08T09:14:10Z", dataType: "json",
data: []byte(`{"messages":[{"User":{"content":[{"Text":"Hello"}]}}]}`),
},
{
id: deletedID, summary: "Doomed thread",
updatedAt: "2026-06-08T09:15:10Z", dataType: "json",
data: []byte(`{"messages":[{"User":{"content":[{"Text":"Bye"}]}}]}`),
},
})
provider, ok := NewProvider(AgentZed, ProviderConfig{
Roots: []string{root},
Machine: "devbox",
})
require.True(t, ok)
survivingPath := ZedSQLiteVirtualPath(dbPath, survivingID)
deletedPath := ZedSQLiteVirtualPath(dbPath, deletedID)
db, err := sql.Open("sqlite3", dbPath)
require.NoError(t, err)
_, err = db.Exec(`DELETE FROM threads WHERE id = ?`, deletedID)
require.NoError(t, err)
require.NoError(t, db.Close())
changed, err := provider.SourcesForChangedPath(
context.Background(),
ChangedPathRequest{
Path: dbPath,
EventKind: "write",
WatchRoot: threadsDir,
StoredSourcePaths: []string{survivingPath, deletedPath},
},
)
require.NoError(t, err)
gotPaths := make([]string, len(changed))
for i, src := range changed {
gotPaths[i] = src.DisplayPath
}
assert.ElementsMatch(t, []string{dbPath, deletedPath}, gotPaths,
"whole-DB source plus a tombstone for the deleted thread only")
var tombstone SourceRef
for _, src := range changed {
if src.DisplayPath == deletedPath {
tombstone = src
}
}
require.NotEmpty(t, tombstone.DisplayPath, "deleted-thread tombstone source")
// The fingerprint of a deleted-but-present-DB thread must be keyed-empty:
// it must not error (or the engine aborts before Parse) and must not carry
// the physical DB size/mtime/hash. A DB-level fingerprint here lets the
// engine's pre-parse freshness check skip Parse whenever stored metadata
// happens to match, stranding the deleted thread. This mirrors Shelley and
// Kiro tombstone fingerprinting.
fingerprint, err := provider.Fingerprint(context.Background(), tombstone)
require.NoError(t, err, "missing-thread fingerprint must not error")
assert.Equal(t, tombstone.FingerprintKey, fingerprint.Key)
assert.Zero(t, fingerprint.Size,
"deleted-thread fingerprint must not carry the DB size")
assert.Zero(t, fingerprint.MTimeNS,
"deleted-thread fingerprint must not carry the DB mtime")
assert.Empty(t, fingerprint.Hash,
"deleted-thread fingerprint must not carry the DB hash")
outcome, err := provider.Parse(context.Background(), ParseRequest{
Source: tombstone,
Fingerprint: fingerprint,
})
require.NoError(t, err)
assert.True(t, outcome.ResultSetComplete)
assert.True(t, outcome.ForceReplace,
"a thread deleted from a present DB is force-replaced out of the archive")
assert.Equal(t, SkipNoSession, outcome.SkipReason)
assert.Empty(t, outcome.Results)
require.NoError(t, os.Remove(dbPath))
gone, err := provider.SourcesForChangedPath(
context.Background(),
ChangedPathRequest{
Path: dbPath,
EventKind: "remove",
WatchRoot: threadsDir,
StoredSourcePaths: []string{survivingPath, deletedPath},
},
)
require.NoError(t, err)
for _, src := range gone {
assert.NotEqual(t, deletedPath, src.DisplayPath,
"a vanished database must not tombstone stored sessions")
assert.NotEqual(t, survivingPath, src.DisplayPath)
}
}
func TestZedProviderRejectsInvalidStoredVirtualPaths(t *testing.T) {
root := t.TempDir()
dbPath := filepath.Join(root, zedThreadsDBRelPath)
threadID := "10431c84-c47b-4e6c-b2df-f9f3b9ad025b"
require.NoError(t, os.MkdirAll(filepath.Dir(dbPath), 0o755))
createZedThreadsDBAt(t, dbPath, []zedTestThread{{
id: threadID,
summary: "Provider thread",
updatedAt: "2026-06-08T09:14:10Z",
dataType: "json",
data: []byte(`{"messages":[{"User":{"content":[{"Text":"Hello Zed"}]}}]}`),
}})
provider, ok := NewProvider(AgentZed, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
for _, path := range []string{
dbPath + "#",
filepath.Join(root, "threads", "threads-copy.db") + "#" + threadID,
filepath.Join(root, "debug", "threads.db") + "#" + threadID,
} {
_, ok, err := provider.FindSource(context.Background(), FindSourceRequest{
StoredFilePath: path,
RequireFreshSource: true,
})
require.NoError(t, err)
assert.False(t, ok, "stored path %q", path)
}
}
func TestZedProviderIgnoresUnrelatedSidecarBasename(t *testing.T) {
root := t.TempDir()
provider, ok := NewProvider(AgentZed, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
changed, err := provider.SourcesForChangedPath(
context.Background(),
ChangedPathRequest{
Path: filepath.Join(root, "other", "threads.db-wal"),
EventKind: "remove",
WatchRoot: filepath.Join(root, "other"),
},
)
require.NoError(t, err)
assert.Empty(t, changed)
}
func TestShelleyProviderCapabilities(t *testing.T) {
factory, ok := ProviderFactoryByType(AgentShelley)
require.True(t, ok)
require.NotNil(t, factory)
caps := factory.Capabilities()
assert.Equal(t, CapabilitySupported, caps.Content.Relationships)
assert.Equal(t, CapabilityUnsupported, caps.Content.AggregateUsageEvents)
provider, ok := NewProvider(AgentShelley, ProviderConfig{
Roots: []string{t.TempDir()},
Machine: "devbox",
})
require.True(t, ok)
require.NotNil(t, provider)
}
func TestShelleyProviderSourceMethods(t *testing.T) {
fixture := shelleyProviderReadFixture(t)
root := fixture.Root
dbPath := fixture.DBPath
virtualPath := ShelleyVirtualPath(dbPath, "cMAIN1")
provider, ok := NewProvider(AgentShelley, ProviderConfig{
Roots: []string{root},
Machine: "devbox",
})
require.True(t, ok)
plan, err := provider.WatchPlan(context.Background())
require.NoError(t, err)
require.Len(t, plan.Roots, 1)
assert.Equal(t, root, plan.Roots[0].Path)
assert.False(t, plan.Roots[0].Recursive)
assert.Equal(t, []string{shelleyDBName, shelleyDBName + "-*"}, plan.Roots[0].IncludeGlobs)
discovered, err := provider.Discover(context.Background())
require.NoError(t, err)
require.Len(t, discovered, 1)
assert.Equal(t, AgentShelley, discovered[0].Provider)
assert.Equal(t, dbPath, discovered[0].DisplayPath)
assert.Equal(t, dbPath, discovered[0].FingerprintKey)
found, ok, err := provider.FindSource(context.Background(), FindSourceRequest{
FullSessionID: "host~shelley:cMAIN1",
})
require.NoError(t, err)
require.True(t, ok)
assert.Equal(t, virtualPath, found.DisplayPath)
assert.Equal(t, virtualPath, found.FingerprintKey)
fingerprint, err := provider.Fingerprint(context.Background(), found)
require.NoError(t, err)
assert.Equal(t, virtualPath, fingerprint.Key)
assert.Positive(t, fingerprint.Size)
assert.NotZero(t, fingerprint.MTimeNS)
assert.NotEmpty(t, fingerprint.Hash)
changed, err := provider.SourcesForChangedPath(
context.Background(),
ChangedPathRequest{Path: dbPath + "-shm", EventKind: "write", WatchRoot: root},
)
require.NoError(t, err)
require.Len(t, changed, 1)
assert.Equal(t, dbPath, changed[0].DisplayPath)
}
func TestShelleyProviderParsePhysicalAndVirtualSources(t *testing.T) {
fixture := shelleyProviderReadFixture(t)
root := fixture.Root
dbPath := fixture.DBPath
provider, ok := NewProvider(AgentShelley, ProviderConfig{
Roots: []string{root},
Machine: "devbox",
})
require.True(t, ok)
sources, err := provider.Discover(context.Background())
require.NoError(t, err)
require.Len(t, sources, 1)
allOutcome, err := provider.Parse(context.Background(), ParseRequest{
Source: sources[0],
})
require.NoError(t, err)
require.True(t, allOutcome.ResultSetComplete)
require.True(t, allOutcome.ForceReplace)
require.Len(t, allOutcome.Results, 2)
assert.Equal(t, "shelley:cAUX1", allOutcome.Results[0].Result.Session.ID)
assert.Equal(t, "shelley:cMAIN1", allOutcome.Results[1].Result.Session.ID)
virtualSource, ok, err := provider.FindSource(context.Background(), FindSourceRequest{
StoredFilePath: ShelleyVirtualPath(dbPath, "cMAIN1"),
})
require.NoError(t, err)
require.True(t, ok)
oneOutcome, err := provider.Parse(context.Background(), ParseRequest{
Source: virtualSource,
})
require.NoError(t, err)
require.True(t, oneOutcome.ResultSetComplete)
require.True(t, oneOutcome.ForceReplace)
require.Len(t, oneOutcome.Results, 1)
assert.Equal(t, "shelley:cMAIN1", oneOutcome.Results[0].Result.Session.ID)
assert.Equal(t, "devbox", oneOutcome.Results[0].Result.Session.Machine)
assert.Len(t, oneOutcome.Results[0].Result.Messages, 5)
}
func TestShelleyProviderFingerprintChangesForSameSecondRewrite(t *testing.T) {
root, _, db := newShelleyTestDB(t)
seedShelleyMainConversation(t, db)
provider, ok := NewProvider(AgentShelley, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
source, ok, err := provider.FindSource(context.Background(), FindSourceRequest{
RawSessionID: "cMAIN1",
})
require.NoError(t, err)
require.True(t, ok)
before, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
_, err = db.Exec(
`UPDATE messages
SET llm_data = ?
WHERE conversation_id = ? AND sequence_id = ?`,
`{"Role":1,"Content":[{"Type":2,"Text":"Changed content."}]}`,
"cMAIN1",
4,
)
require.NoError(t, err)
after, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
assert.Equal(t, before.MTimeNS, after.MTimeNS)
assert.NotEqual(t, before.Hash, after.Hash)
}
func TestShelleyProviderFingerprintIncludesWALSiblings(t *testing.T) {
root, dbPath, db := newShelleyTestDB(t)
seedShelleyMainConversation(t, db)
provider, ok := NewProvider(AgentShelley, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
sources, err := provider.Discover(context.Background())
require.NoError(t, err)
require.Len(t, sources, 1)
before, err := provider.Fingerprint(context.Background(), sources[0])
require.NoError(t, err)
walPath := dbPath + "-wal"
writeSourceFile(t, walPath, "wal")
walTime := time.Unix(0, before.MTimeNS+int64(time.Second))
require.NoError(t, os.Chtimes(walPath, walTime, walTime))
after, err := provider.Fingerprint(context.Background(), sources[0])
require.NoError(t, err)
assert.Equal(t, before.Size, after.Size)
assert.Greater(t, after.MTimeNS, before.MTimeNS)
}
func TestShelleyProviderClassifiesDeletedVirtualPath(t *testing.T) {
root, dbPath, db := newShelleyTestDB(t)
seedShelleyMainConversation(t, db)
virtualPath := ShelleyVirtualPath(dbPath, "cMAIN1")
// Close the setup handle before deleting; Windows will not unlink a file
// this process still holds open.
require.NoError(t, db.Close())
require.NoError(t, os.Remove(dbPath))
provider, ok := NewProvider(AgentShelley, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
changed, err := provider.SourcesForChangedPath(
context.Background(),
ChangedPathRequest{Path: virtualPath, EventKind: "remove", WatchRoot: root},
)
require.NoError(t, err)
require.Len(t, changed, 1)
assert.Equal(t, virtualPath, changed[0].DisplayPath)
}
func TestShelleyProviderClassifiesDeletedPhysicalDB(t *testing.T) {
root, dbPath, db := newShelleyTestDB(t)
seedShelleyMainConversation(t, db)
// Close the setup handle before deleting; Windows will not unlink a file
// this process still holds open.
require.NoError(t, db.Close())
require.NoError(t, os.Remove(dbPath))
provider, ok := NewProvider(AgentShelley, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
changed, err := provider.SourcesForChangedPath(
context.Background(),
ChangedPathRequest{Path: dbPath, EventKind: "remove", WatchRoot: root},
)
require.NoError(t, err)
require.Len(t, changed, 1)
assert.Equal(t, dbPath, changed[0].DisplayPath)
outcome, err := provider.Parse(context.Background(), ParseRequest{Source: changed[0]})
require.NoError(t, err)
assert.True(t, outcome.ResultSetComplete)
// The backing DB file is gone, so the outcome must not force-replace: the
// persistent archive preserves sessions whose source file no longer exists.
assert.False(t, outcome.ForceReplace)
assert.Equal(t, SkipNoSession, outcome.SkipReason)
assert.Empty(t, outcome.Results)
}
// TestShelleyProviderChangedPathTombstonesDeletedConversation verifies the
// changed-path classifier emits a tombstone for a stored Shelley conversation
// deleted from a still-present database, and that the tombstone's fingerprint
// no longer errors (it previously aborted before Parse and stranded the stale
// session). The surviving conversation is left to the whole-DB fan-out; a
// vanished database emits no tombstone.
func TestShelleyProviderChangedPathTombstonesDeletedConversation(t *testing.T) {
root, dbPath, db := newShelleyTestDB(t)
seedShelleyMainConversation(t, db) // cMAIN1 survives
seedShelleyConversation(
t, db, "cDEL1", "Doomed conversation",
"/home/user/dev/myapp", "claude-sonnet-4-6", "", true,
"2026-06-15T11:00:00Z", "2026-06-15T11:05:00Z",
)
seedShelleyMessage(t, db, "cDEL1", 1, 1, "user",
`{"Role":0,"Content":[{"Type":2,"Text":"hi"}]}`, "", "",
"2026-06-15T11:00:00Z")
provider, ok := NewProvider(AgentShelley, ProviderConfig{
Roots: []string{root},
Machine: "devbox",
})
require.True(t, ok)
survivingPath := ShelleyVirtualPath(dbPath, "cMAIN1")
deletedPath := ShelleyVirtualPath(dbPath, "cDEL1")
_, err := db.Exec(`DELETE FROM messages WHERE conversation_id = ?`, "cDEL1")
require.NoError(t, err)
_, err = db.Exec(`DELETE FROM conversations WHERE conversation_id = ?`, "cDEL1")
require.NoError(t, err)
changed, err := provider.SourcesForChangedPath(
context.Background(),
ChangedPathRequest{
Path: dbPath,
EventKind: "write",
WatchRoot: root,
StoredSourcePaths: []string{survivingPath, deletedPath},
},
)
require.NoError(t, err)
gotPaths := make([]string, len(changed))
for i, src := range changed {
gotPaths[i] = src.DisplayPath
}
assert.ElementsMatch(t, []string{dbPath, deletedPath}, gotPaths,
"whole-DB source plus a tombstone for the deleted conversation only")
var tombstone SourceRef
for _, src := range changed {
if src.DisplayPath == deletedPath {
tombstone = src
}
}
require.NotEmpty(t, tombstone.DisplayPath, "deleted-conversation tombstone source")
// The fingerprint of a deleted-but-present-DB member must not error, or the
// engine aborts before Parse and the stale session is never dropped.
fingerprint, err := provider.Fingerprint(context.Background(), tombstone)
require.NoError(t, err, "missing-member fingerprint must not error")
assert.Equal(t, tombstone.FingerprintKey, fingerprint.Key)
outcome, err := provider.Parse(context.Background(), ParseRequest{
Source: tombstone,
Fingerprint: fingerprint,
})
require.NoError(t, err)
assert.True(t, outcome.ResultSetComplete)
assert.True(t, outcome.ForceReplace,
"a conversation deleted from a present DB is force-replaced out of the archive")
assert.Equal(t, SkipNoSession, outcome.SkipReason)
assert.Empty(t, outcome.Results)
// A vanished database file emits no tombstone (stored sessions preserved).
require.NoError(t, db.Close())
require.NoError(t, os.Remove(dbPath))
gone, err := provider.SourcesForChangedPath(
context.Background(),
ChangedPathRequest{
Path: dbPath,
EventKind: "remove",
WatchRoot: root,
StoredSourcePaths: []string{survivingPath, deletedPath},
},
)
require.NoError(t, err)
for _, src := range gone {
assert.NotEqual(t, deletedPath, src.DisplayPath,
"a vanished database must not tombstone stored sessions")
assert.NotEqual(t, survivingPath, src.DisplayPath)
}
}
func TestShelleyProviderStoredVirtualPathFreshness(t *testing.T) {
root, dbPath, db := newShelleyTestDB(t)
seedShelleyMainConversation(t, db)
virtualPath := ShelleyVirtualPath(dbPath, "cMAIN1")
provider, ok := NewProvider(AgentShelley, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
found, ok, err := provider.FindSource(context.Background(), FindSourceRequest{
StoredFilePath: virtualPath,
RequireFreshSource: true,
})
require.NoError(t, err)
require.True(t, ok)
assert.Equal(t, virtualPath, found.DisplayPath)
_, err = db.Exec(`DELETE FROM messages WHERE conversation_id = ?`, "cMAIN1")
require.NoError(t, err)
_, err = db.Exec(`DELETE FROM conversations WHERE conversation_id = ?`, "cMAIN1")
require.NoError(t, err)
_, ok, err = provider.FindSource(context.Background(), FindSourceRequest{
StoredFilePath: virtualPath,
RequireFreshSource: true,
})
require.NoError(t, err)
assert.False(t, ok, "fresh lookup must reject a deleted virtual row")
staleSource, ok, err := provider.FindSource(context.Background(), FindSourceRequest{
StoredFilePath: virtualPath,
})
require.NoError(t, err)
require.True(t, ok, "non-fresh lookup keeps tombstone source identity")
outcome, err := provider.Parse(context.Background(), ParseRequest{
Source: staleSource,
})
require.NoError(t, err)
assert.True(t, outcome.ResultSetComplete)
assert.True(t, outcome.ForceReplace)
assert.Equal(t, SkipNoSession, outcome.SkipReason)
assert.Empty(t, outcome.Results)
}
func TestShelleyProviderRejectsInvalidStoredVirtualPaths(t *testing.T) {
root, dbPath, db := newShelleyTestDB(t)
seedShelleyMainConversation(t, db)
provider, ok := NewProvider(AgentShelley, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
for _, path := range []string{
dbPath + "#",
filepath.Join(root, "shelley-debug.db") + "#cMAIN1",
filepath.Join(root, "nested", shelleyDBName) + "#cMAIN1",
} {
_, ok, err := provider.FindSource(context.Background(), FindSourceRequest{
StoredFilePath: path,
RequireFreshSource: true,
})
require.NoError(t, err)
assert.False(t, ok, "stored path %q", path)
}
}
func TestShelleyProviderIgnoresUnrelatedSidecarBasename(t *testing.T) {
root := t.TempDir()
provider, ok := NewProvider(AgentShelley, ProviderConfig{Roots: []string{root}})
require.True(t, ok)
changed, err := provider.SourcesForChangedPath(
context.Background(),
ChangedPathRequest{
Path: filepath.Join(root, "nested", shelleyDBName+"-wal"),
EventKind: "remove",
WatchRoot: filepath.Join(root, "nested"),
},
)
require.NoError(t, err)
assert.Empty(t, changed)
}