Files
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

1108 lines
36 KiB
Go

package db
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.kenn.io/agentsview/internal/export"
)
func TestProjectObservationDatabaseIDIsCreatedAndStable(t *testing.T) {
d := testDB(t)
ctx := context.Background()
first, err := d.GetDatabaseID(ctx)
require.NoError(t, err)
assert.Regexp(t, regexp.MustCompile(
`^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`,
), first)
second, err := d.GetOrCreateDatabaseID(ctx)
require.NoError(t, err)
assert.Equal(t, first, second)
require.NoError(t, d.SetDatabaseIDForTest(ctx, "test-database-id"))
overridden, err := d.GetOrCreateDatabaseID(ctx)
require.NoError(t, err)
assert.Equal(t, "test-database-id", overridden)
}
func TestProjectObservationArchiveIDIsCreatedAndStable(t *testing.T) {
d := testDB(t)
ctx := context.Background()
first, err := d.GetArchiveID(ctx)
require.NoError(t, err)
assert.Regexp(t, regexp.MustCompile(
`^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`,
), first)
second, err := d.GetOrCreateArchiveID(ctx)
require.NoError(t, err)
assert.Equal(t, first, second)
}
func TestProjectIdentityPublicationRevisionTracksSnapshotChanges(t *testing.T) {
d := testDB(t)
ctx := context.Background()
before, err := d.ProjectIdentityPublicationRevision(ctx)
require.NoError(t, err)
require.NoError(t, d.UpsertSession(Session{
ID: "revision-session", Project: "app", Machine: "local", Agent: "codex",
}))
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
SessionID: "revision-session", Project: "app", Machine: "local",
RootPath: "/tmp/app", ObservedAt: time.Now().UTC(),
}))
afterInsert, err := d.ProjectIdentityPublicationRevision(ctx)
require.NoError(t, err)
assert.Greater(t, afterInsert, before)
require.NoError(t, d.DeleteSession("revision-session"))
afterDelete, err := d.ProjectIdentityPublicationRevision(ctx)
require.NoError(t, err)
assert.Greater(t, afterDelete, afterInsert)
}
func TestSessionProjectIdentitySnapshotPreservesFirstRootKeyUntilRemoteEvidence(
t *testing.T,
) {
d := testDB(t)
ctx := context.Background()
require.NoError(t, d.UpsertSession(Session{
ID: "root-snapshot", Project: "app", Machine: "local", Agent: "codex",
}))
firstObservedAt := time.Date(2026, 7, 1, 12, 0, 0, 0, time.UTC)
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
SessionID: "root-snapshot", Project: "app", Machine: "local",
RootPath: "/first/app", ObservedAt: firstObservedAt,
}))
first, err := d.listSessionProjectIdentitySnapshots(
ctx, []string{"root-snapshot"},
)
require.NoError(t, err)
require.Contains(t, first, "root-snapshot")
require.NotEmpty(t, first["root-snapshot"].Key)
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
SessionID: "root-snapshot", Project: "app", Machine: "local",
RootPath: "/later/app", ObservedAt: firstObservedAt.Add(time.Hour),
}))
afterRoot, err := d.listSessionProjectIdentitySnapshots(
ctx, []string{"root-snapshot"},
)
require.NoError(t, err)
assert.Equal(t, "/first/app", afterRoot["root-snapshot"].RootPath)
assert.Equal(t, first["root-snapshot"].Key,
afterRoot["root-snapshot"].Key)
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
SessionID: "root-snapshot", Project: "app", Machine: "local",
RootPath: "/later/app", GitRemote: "https://example.com/example/app.git",
ObservedAt: firstObservedAt.Add(2 * time.Hour),
}))
afterRemote, err := d.listSessionProjectIdentitySnapshots(
ctx, []string{"root-snapshot"},
)
require.NoError(t, err)
assert.Equal(t, export.ProjectResolutionResolved,
afterRemote["root-snapshot"].RemoteResolution)
assert.Equal(t, "https://example.com/example/app.git",
afterRemote["root-snapshot"].GitRemote)
assert.NotEqual(t, first["root-snapshot"].Key,
afterRemote["root-snapshot"].Key)
require.NoError(t, d.UpsertSession(Session{
ID: "ambiguous-snapshot", Project: "app", Machine: "local", Agent: "codex",
}))
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
SessionID: "ambiguous-snapshot", Project: "app", Machine: "local",
RootPath: "/first/ambiguous", ObservedAt: firstObservedAt,
}))
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
SessionID: "ambiguous-snapshot", Project: "app", Machine: "local",
RootPath: "/later/ambiguous", RemoteCandidateCount: 2,
RemoteResolution: export.ProjectResolutionAmbiguous,
ObservedAt: firstObservedAt.Add(time.Hour),
}))
afterAmbiguous, err := d.listSessionProjectIdentitySnapshots(
ctx, []string{"ambiguous-snapshot"},
)
require.NoError(t, err)
assert.Equal(t, export.ProjectResolutionAmbiguous,
afterAmbiguous["ambiguous-snapshot"].RemoteResolution)
assert.Equal(t, 2,
afterAmbiguous["ambiguous-snapshot"].RemoteCandidateCount)
}
func TestLoadProjectIdentityPublicationDeltaReturnsRowsAndTombstones(
t *testing.T,
) {
d := testDB(t)
ctx := context.Background()
observedAt := time.Date(2026, 7, 11, 12, 0, 0, 0, time.UTC)
for _, project := range []string{"alpha", "beta"} {
sessionID := "identity-" + project
require.NoError(t, d.UpsertSession(Session{
ID: sessionID, Project: project, Machine: "local", Agent: "codex",
}))
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
SessionID: sessionID, Project: project, Machine: "local",
RootPath: "/workspace/" + project,
GitRemote: "https://example.com/" + project + ".git",
GitRemoteName: "origin",
RemoteResolution: export.ProjectResolutionResolved,
ObservedAt: observedAt,
},
))
}
baseline, err := d.ProjectIdentityPublicationRevision(ctx)
require.NoError(t, err)
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
SessionID: "identity-alpha", Project: "alpha", Machine: "local",
RootPath: "/workspace/alpha",
GitRemote: "https://example.com/alpha.git",
GitRemoteName: "upstream",
RemoteResolution: export.ProjectResolutionResolved,
ObservedAt: observedAt.Add(time.Hour),
},
))
_, err = d.rawWriter().ExecContext(ctx, `
DELETE FROM project_identity_observations
WHERE project = ? AND machine = ? AND root_path = ? AND git_remote = ?`,
"beta", "local", "/workspace/beta", "https://example.com/beta.git",
)
require.NoError(t, err)
require.NoError(t, d.DeleteSession("identity-alpha"))
through, err := d.ProjectIdentityPublicationRevision(ctx)
require.NoError(t, err)
delta, err := d.LoadProjectIdentityPublicationDelta(
ctx, baseline, through, nil, nil,
)
require.NoError(t, err)
require.Len(t, delta.Observations, 1)
assert.Equal(t, "alpha", delta.Observations[0].Project)
assert.Equal(t, "upstream", delta.Observations[0].GitRemoteName)
assert.Equal(t, []ProjectIdentityObservationKey{{
Project: "beta", Machine: "local", RootPath: "/workspace/beta",
GitRemote: "https://example.com/beta.git",
}}, delta.ObservationDeletes)
assert.Empty(t, delta.Snapshots)
assert.Equal(t, []SessionProjectIdentitySnapshotKey{{
SessionID: "identity-alpha", Project: "alpha",
}}, delta.SnapshotDeletes)
betaDelta, err := d.LoadProjectIdentityPublicationDelta(
ctx, baseline, through, []string{"beta"}, nil,
)
require.NoError(t, err)
assert.Empty(t, betaDelta.Observations)
assert.Equal(t, delta.ObservationDeletes, betaDelta.ObservationDeletes)
assert.Empty(t, betaDelta.Snapshots)
assert.Empty(t, betaDelta.SnapshotDeletes)
}
func TestCopyArchiveIdentityFromPreservesLogicalArchiveAndNewGeneration(
t *testing.T,
) {
ctx := context.Background()
dir := t.TempDir()
sourcePath := filepath.Join(dir, "source.db")
source, err := Open(sourcePath)
require.NoError(t, err)
require.NoError(t, source.SetDatabaseIDForTest(ctx, "source-generation"))
require.NoError(t, source.SetArchiveIdentityForTest(
ctx, "stable-archive", strings.Repeat("a", 64),
))
require.NoError(t, source.Close())
target := testDB(t)
require.NoError(t, target.SetDatabaseIDForTest(ctx, "new-generation"))
require.NoError(t, target.SetArchiveIdentityForTest(
ctx, "temporary-archive", strings.Repeat("b", 64),
))
require.NoError(t, target.CopyArchiveIdentityFrom(sourcePath))
archiveID, err := target.GetArchiveID(ctx)
require.NoError(t, err)
assert.Equal(t, "stable-archive", archiveID)
archiveSalt, err := target.GetArchiveSalt(ctx)
require.NoError(t, err)
assert.Equal(t, strings.Repeat("a", 64), archiveSalt)
databaseID, err := target.GetDatabaseID(ctx)
require.NoError(t, err)
assert.Equal(t, "new-generation", databaseID)
}
func TestProjectObservationArchiveSaltIsCreatedAndStable(t *testing.T) {
d := testDB(t)
ctx := context.Background()
first, err := d.GetArchiveSalt(ctx)
require.NoError(t, err)
assert.Regexp(t, regexp.MustCompile(`^[0-9a-f]{64}$`), first)
second, err := d.GetOrCreateArchiveSalt(ctx)
require.NoError(t, err)
assert.Equal(t, first, second)
}
func TestProjectObservationArchiveSaltRejectsCorruptPersistedValue(t *testing.T) {
d := testDB(t)
ctx := context.Background()
require.NoError(t, d.SetArchiveIdentityForTest(ctx, "archive", "not-a-salt"))
_, err := d.GetArchiveSalt(ctx)
require.ErrorIs(t, err, ErrArchiveSaltInvalid)
_, err = d.GetOrCreateArchiveSalt(ctx)
require.ErrorIs(t, err, ErrArchiveSaltInvalid,
"a corrupt established salt must not be silently replaced")
}
func TestCopyArchiveIdentityFromRejectsCorruptSalt(t *testing.T) {
ctx := context.Background()
sourcePath := filepath.Join(t.TempDir(), "source.db")
source, err := Open(sourcePath)
require.NoError(t, err)
require.NoError(t, source.SetArchiveIdentityForTest(
ctx, "archive", "truncated",
))
require.NoError(t, source.Close())
target := testDB(t)
err = target.CopyArchiveIdentityFrom(sourcePath)
require.ErrorIs(t, err, ErrArchiveSaltInvalid)
}
func TestProjectIdentityObservationRoundTripsRepositoryContext(t *testing.T) {
d := testDB(t)
ctx := context.Background()
want := export.ProjectIdentityObservation{
SourceArchiveID: "archive-source",
SourceArchiveSalt: "archive-salt",
Project: "app",
Machine: "host",
RootPath: "/private/app/worktree",
RepositoryPath: "/private/app/.git",
WorktreeRootPath: "/private/app/worktree",
WorktreeRelationship: export.WorktreeLinked,
CheckoutState: export.CheckoutDetached,
GitBranch: "",
RemoteResolution: export.ProjectResolutionAmbiguous,
RemoteCandidateCount: 2,
ObservedAt: time.Date(2026, 7, 10, 12, 0, 0, 0, time.UTC),
}
require.NoError(t, d.UpsertProjectIdentityObservation(ctx, want))
got, err := d.ListProjectIdentityObservations(ctx, []string{"app"})
require.NoError(t, err)
require.Len(t, got, 1)
assert.Equal(t, want.RepositoryPath, got[0].RepositoryPath)
assert.Equal(t, want.SourceArchiveID, got[0].SourceArchiveID)
assert.Equal(t, want.SourceArchiveSalt, got[0].SourceArchiveSalt)
assert.Equal(t, want.WorktreeRelationship, got[0].WorktreeRelationship)
assert.Equal(t, want.CheckoutState, got[0].CheckoutState)
assert.Equal(t, want.RemoteResolution, got[0].RemoteResolution)
assert.Equal(t, want.RemoteCandidateCount, got[0].RemoteCandidateCount)
}
func TestProjectObservationDatabaseIDInitializedForReadOnlyOpen(t *testing.T) {
path := filepath.Join(t.TempDir(), "test.db")
writable := testDBAtPath(t, path, "read-only database id seed")
seeded, err := writable.GetDatabaseID(context.Background())
require.NoError(t, err)
require.NotEmpty(t, seeded)
require.NoError(t, writable.Close())
readonly, err := OpenReadOnly(path)
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, readonly.Close()) })
got, err := readonly.GetDatabaseID(context.Background())
require.NoError(t, err)
assert.Equal(t, seeded, got)
}
func TestProjectObservationDatabaseIDReadOnlyExisting(t *testing.T) {
path := filepath.Join(t.TempDir(), "test.db")
writable := testDBAtPath(t, path, "read-only database id seed")
require.NoError(t, writable.SetDatabaseIDForTest(
context.Background(), "read-only-db-id"))
require.NoError(t, writable.Close())
readonly, err := OpenReadOnly(path)
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, readonly.Close()) })
got, err := readonly.GetDatabaseID(context.Background())
require.NoError(t, err)
assert.Equal(t, "read-only-db-id", got)
}
func TestProjectObservationDatabaseIDMissingDoesNotCreate(t *testing.T) {
d := testDB(t)
_, err := d.rawWriter().Exec(`
DELETE FROM archive_metadata WHERE key = ?`,
archiveMetadataDatabaseIDKey,
)
require.NoError(t, err)
_, err = d.GetDatabaseID(context.Background())
require.ErrorIs(t, err, ErrDatabaseIDMissing)
var count int
require.NoError(t, d.rawReader().QueryRow(`
SELECT COUNT(*) FROM archive_metadata
WHERE key = ?`, archiveMetadataDatabaseIDKey).Scan(&count))
assert.Zero(t, count)
}
func TestProjectObservationRawValuesAreAuthoritativeForExportKeys(t *testing.T) {
d := testDB(t)
ctx := context.Background()
obs := export.ProjectIdentityObservation{
Project: "app",
Machine: "laptop",
RootPath: "/tmp/app",
GitRemote: "git@github.com:Org/Repo.git",
GitRemoteName: "origin",
ObservedAt: time.Date(2026, 7, 3, 12, 0, 0, 0, time.UTC),
NormalizedRemote: "stale/normalized",
KeySource: "root_path",
Key: "stale-key",
}
require.NoError(t, d.UpsertProjectIdentityObservation(ctx, obs))
got, err := d.ListProjectIdentityObservations(ctx, []string{"app"})
require.NoError(t, err)
require.Len(t, got, 1)
assert.Equal(t, "github.com:Org/Repo.git", got[0].GitRemote)
assert.Equal(t, "/tmp/app", got[0].RootPath)
projects := export.BuildProjectsMap([]string{"app"}, got)
require.Equal(t, export.ProjectResolutionResolved, projects["app"].Resolution)
require.NotNil(t, projects["app"].Identity)
assert.Equal(t, "github.com/Org/Repo", projects["app"].Identity.NormalizedRemote)
assert.Equal(t, export.ProjectKindGitRemote, projects["app"].Identity.Kind)
assert.NotEmpty(t, projects["app"].Identity.Key)
}
func TestProjectObservationStripsGitRemoteCredentialsBeforeStorage(t *testing.T) {
d := testDB(t)
ctx := context.Background()
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
Project: "app",
Machine: "laptop",
RootPath: "/tmp/app",
GitRemote: "https://" + "user:token@" + "example.com/acme/app.git",
GitRemoteName: "origin",
},
))
got, err := d.ListProjectIdentityObservations(ctx, []string{"app"})
require.NoError(t, err)
require.Len(t, got, 1)
assert.Equal(t, "https://example.com/acme/app.git", got[0].GitRemote)
assert.Equal(t, "example.com/acme/app", got[0].NormalizedRemote)
assert.Equal(t, export.ProjectIdentityKeySourceGitRemote, got[0].KeySource)
}
func TestProjectObservationMigrationStripsStoredGitRemoteCredentials(t *testing.T) {
path := filepath.Join(t.TempDir(), "test.db")
d := testDBAtPath(t, path, "credential migration seed")
_, err := d.rawWriter().Exec(`
INSERT INTO project_identity_observations (
project, machine, root_path, git_remote, git_remote_name,
worktree_name, worktree_root_path, observed_at,
normalized_remote, key_source, key
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
"app", "laptop", "/tmp/app",
"https://"+"user:token@"+"example.com/acme/app.git", "origin",
"", "", "2026-07-03T12:00:00Z",
"example.com/acme/app", export.ProjectIdentityKeySourceGitRemote,
projectIdentitySHA("git_remote\n"+"example.com/acme/app"),
)
require.NoError(t, err)
_, err = d.rawWriter().Exec(`DELETE FROM stats WHERE key = ?`,
projectIdentityRemoteScrubCompletedKey)
require.NoError(t, err)
require.NoError(t, d.Close())
reopened, err := Open(path)
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, reopened.Close()) })
got, err := reopened.ListProjectIdentityObservations(
context.Background(), []string{"app"})
require.NoError(t, err)
require.Len(t, got, 1)
assert.Equal(t, "https://example.com/acme/app.git", got[0].GitRemote)
assert.Equal(t, "example.com/acme/app", got[0].NormalizedRemote)
var scrubbed string
require.NoError(t, reopened.getReader().QueryRowContext(
context.Background(), `SELECT value FROM stats WHERE key = ?`,
projectIdentityRemoteScrubCompletedKey,
).Scan(&scrubbed))
assert.Equal(t, "1", scrubbed)
}
func TestProjectObservationListFiltersLabelsAndKeepsPersistedRemoteMachineRows(t *testing.T) {
d := testDB(t)
ctx := context.Background()
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
Project: "old-project",
Machine: "remote-host",
RootPath: "remote-host:/srv/app",
GitRemote: "https://github.com/acme/old-project.git",
},
))
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
Project: "other",
Machine: "remote-host",
RootPath: "s3://bucket/archive",
GitRemote: "https://github.com/acme/other.git",
},
))
got, err := d.ListProjectIdentityObservations(ctx, []string{"old-project"})
require.NoError(t, err)
require.Len(t, got, 1)
assert.Equal(t, "remote-host", got[0].Machine)
assert.Equal(t, "remote-host:/srv/app", got[0].RootPath)
assert.Equal(t, "https://github.com/acme/old-project.git", got[0].GitRemote)
}
func TestProjectIdentityNilLabelsListAllButEmptyLabelsMapEmpty(t *testing.T) {
d := testDB(t)
ctx := context.Background()
seedProjectIdentityObservation(t, d, "observed-project")
all, err := d.ListProjectIdentityObservations(ctx, nil)
require.NoError(t, err)
require.Len(t, all, 1)
assert.Equal(t, "observed-project", all[0].Project)
empty, err := d.BuildProjectIdentityMap(ctx, []string{})
require.NoError(t, err)
assert.Empty(t, empty)
}
func TestProjectIdentityGoldenFixtureObservationsAreDeterministic(t *testing.T) {
d := testDB(t)
ctx := context.Background()
observedAt := time.Date(2026, 7, 3, 12, 0, 0, 0, time.UTC)
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
Project: "remote-project",
Machine: "golden-host",
RootPath: "/fixtures/remote-project/worktrees/feature",
GitRemote: "https://github.com/acme/remote-project.git",
GitRemoteName: "origin",
WorktreeName: "feature",
WorktreeRootPath: "/fixtures/remote-project",
ObservedAt: observedAt,
},
))
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
Project: "path-project",
Machine: "golden-host",
RootPath: "/fixtures/path-project",
RepositoryPath: "/fixtures/path-project/.git",
WorktreeRelationship: export.WorktreeMain,
ObservedAt: observedAt,
},
))
got, err := d.BuildProjectIdentityMap(ctx,
[]string{"remote-project", "path-project", "unknown-project"})
require.NoError(t, err)
require.Equal(t, export.ProjectResolutionResolved,
got["remote-project"].Resolution)
require.NotNil(t, got["remote-project"].Identity)
assert.Equal(t, export.ProjectKindGitRemote, got["remote-project"].Identity.Kind)
assert.Equal(t, "github.com/acme/remote-project",
got["remote-project"].Identity.NormalizedRemote)
assert.NotEmpty(t, got["remote-project"].Identity.Key)
require.Equal(t, export.ProjectResolutionResolved,
got["path-project"].Resolution)
require.NotNil(t, got["path-project"].Identity)
assert.Equal(t, export.ProjectKindMachineRoot, got["path-project"].Identity.Kind)
assert.Empty(t, got["path-project"].Identity.NormalizedRemote)
encoded, err := json.Marshal(got["path-project"])
require.NoError(t, err)
assert.NotContains(t, string(encoded), "/fixtures/path-project")
assert.Equal(t, export.ProjectResolutionUnknown,
got["unknown-project"].Resolution)
assert.Nil(t, got["unknown-project"].Identity)
}
func TestProjectObservationSessionBatchWritePersistsObservation(t *testing.T) {
d := testDB(t)
ctx := context.Background()
result, err := d.WriteSessionBatch([]SessionBatchWrite{{
Session: Session{
ID: "batch-observation",
Project: "mapped-project",
Machine: "laptop",
Agent: "codex",
},
IdentityObservation: export.ProjectIdentityObservation{
Project: "mapped-project",
Machine: "laptop",
RootPath: "/tmp/worktree",
WorktreeName: "feature",
WorktreeRootPath: "/tmp/worktree",
},
DataVersion: CurrentDataVersion(),
ReplaceMessages: true,
}})
require.NoError(t, err)
assert.Equal(t, 1, result.WrittenSessions)
got, err := d.ListProjectIdentityObservations(ctx, []string{"mapped-project"})
require.NoError(t, err)
require.Len(t, got, 1)
assert.Equal(t, "/tmp/worktree", got[0].RootPath)
assert.Empty(t, got[0].GitRemote)
projects := export.BuildProjectsMap([]string{"mapped-project"}, got)
require.Equal(t, export.ProjectResolutionUnknown, projects["mapped-project"].Resolution)
assert.Nil(t, projects["mapped-project"].Identity)
}
func seedProjectIdentityObservation(t *testing.T, d *DB, project string) {
t.Helper()
require.NoError(t, d.UpsertProjectIdentityObservation(context.Background(),
export.ProjectIdentityObservation{
Project: project,
Machine: "test-machine",
RootPath: "/tmp/" + project,
GitRemote: "https://github.com/acme/" + project + ".git",
},
))
}
func TestProjectObservationRemoteReplacesSameRootFallback(t *testing.T) {
d := testDB(t)
ctx := context.Background()
root := filepath.Join(t.TempDir(), "repo")
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
Project: "app",
Machine: "laptop",
RootPath: root,
},
))
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
Project: "app",
Machine: "laptop",
RootPath: root,
GitRemote: "https://github.com/acme/app.git",
GitRemoteName: "origin",
},
))
got, err := d.ListProjectIdentityObservations(ctx, []string{"app"})
require.NoError(t, err)
require.Len(t, got, 1)
assert.Equal(t, "https://github.com/acme/app.git", got[0].GitRemote)
projects := export.BuildProjectsMap([]string{"app"}, got)
require.Equal(t, export.ProjectResolutionResolved, projects["app"].Resolution)
require.NotNil(t, projects["app"].Identity)
assert.Equal(t, "github.com/acme/app", projects["app"].Identity.NormalizedRemote)
}
func TestProjectObservationFallbackDoesNotRecreateSameRootWhenRemoteExists(t *testing.T) {
d := testDB(t)
ctx := context.Background()
root := filepath.Join(t.TempDir(), "repo")
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
Project: "app",
Machine: "laptop",
RootPath: root,
GitRemote: "https://github.com/acme/app.git",
GitRemoteName: "origin",
},
))
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
Project: "app",
Machine: "laptop",
RootPath: root,
},
))
got, err := d.ListProjectIdentityObservations(ctx, []string{"app"})
require.NoError(t, err)
require.Len(t, got, 1)
assert.Equal(t, "https://github.com/acme/app.git", got[0].GitRemote)
}
func TestProjectObservationAmbiguousSupersedesResolvedAggregate(t *testing.T) {
d := testDB(t)
ctx := context.Background()
root := filepath.Join(t.TempDir(), "repo")
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
Project: "app", Machine: "laptop", RootPath: root,
GitRemote: "https://github.com/acme/app.git",
},
))
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
Project: "app", Machine: "laptop", RootPath: root,
RemoteResolution: export.ProjectResolutionAmbiguous,
RemoteCandidateCount: 2,
},
))
got, err := d.BuildProjectIdentityMap(ctx, []string{"app"})
require.NoError(t, err)
assert.Equal(t, export.ProjectResolutionAmbiguous, got["app"].Resolution)
assert.Nil(t, got["app"].Identity)
}
func TestProjectObservationResolvedDoesNotDiscardAmbiguousEvidence(t *testing.T) {
d := testDB(t)
ctx := context.Background()
root := filepath.Join(t.TempDir(), "repo")
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
Project: "app", Machine: "laptop", RootPath: root,
RemoteResolution: export.ProjectResolutionAmbiguous,
RemoteCandidateCount: 2,
},
))
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
Project: "app", Machine: "laptop", RootPath: root,
GitRemote: "https://github.com/acme/app.git",
},
))
got, err := d.BuildProjectIdentityMap(ctx, []string{"app"})
require.NoError(t, err)
assert.Equal(t, export.ProjectResolutionAmbiguous, got["app"].Resolution)
assert.Nil(t, got["app"].Identity)
}
func TestProjectObservationUnknownDoesNotReplaceAmbiguousEvidence(t *testing.T) {
d := testDB(t)
ctx := context.Background()
root := filepath.Join(t.TempDir(), "repo")
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
Project: "app", Machine: "laptop", RootPath: root,
RemoteResolution: export.ProjectResolutionAmbiguous,
RemoteCandidateCount: 2,
},
))
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
Project: "app", Machine: "laptop", RootPath: root,
RemoteResolution: export.ProjectResolutionUnknown,
},
))
got, err := d.BuildProjectIdentityMap(ctx, []string{"app"})
require.NoError(t, err)
assert.Equal(t, export.ProjectResolutionAmbiguous, got["app"].Resolution)
assert.Nil(t, got["app"].Identity)
}
func TestProjectObservationScrubDowngradesUnusableRemoteToFallback(t *testing.T) {
d := testDB(t)
ctx := context.Background()
root := filepath.Join(t.TempDir(), "repo")
_, err := d.getWriter().ExecContext(ctx, `
INSERT INTO project_identity_observations (
project, machine, root_path, git_remote, git_remote_name,
worktree_name, worktree_root_path, observed_at,
normalized_remote, key_source, key
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
"app", "laptop", root, "file:///tmp/app.git", "origin",
"", "", "2026-07-03T12:00:00Z", "", "", "",
)
require.NoError(t, err)
tx, err := d.getWriter().BeginTx(ctx, nil)
require.NoError(t, err)
require.NoError(t, scrubProjectIdentityGitRemoteCredentialsTx(ctx, tx))
require.NoError(t, tx.Commit())
got, err := d.ListProjectIdentityObservations(ctx, []string{"app"})
require.NoError(t, err)
require.Len(t, got, 1)
assert.Empty(t, got[0].GitRemote)
assert.Equal(t, export.ProjectIdentityKeySourceRootPath, got[0].KeySource)
assert.NotEmpty(t, got[0].Key)
}
func TestProjectObservationExportReadsDoNotMutateArchive(t *testing.T) {
d := testDB(t)
ctx := context.Background()
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
Project: "app",
Machine: "laptop",
RootPath: "/tmp/app",
GitRemote: "https://github.com/acme/app.git",
},
))
before := projectObservationRowCount(t, d)
_, err := d.ListProjectIdentityObservations(ctx, []string{"app"})
require.NoError(t, err)
_, err = d.ListProjectIdentityObservations(ctx, []string{"missing"})
require.NoError(t, err)
assert.Equal(t, before, projectObservationRowCount(t, d))
}
func TestProjectObservationDatabaseIDConcurrentCreateReturnsSingleID(t *testing.T) {
d := testDB(t)
ctx := context.Background()
const workers = 64
var wg sync.WaitGroup
start := make(chan struct{})
ids := make(chan string, workers)
errs := make(chan error, workers)
for range workers {
wg.Go(func() {
<-start
id, err := d.GetOrCreateDatabaseID(ctx)
if err != nil {
errs <- err
return
}
ids <- id
})
}
close(start)
wg.Wait()
close(ids)
close(errs)
require.Empty(t, errs)
seen := map[string]bool{}
for id := range ids {
seen[id] = true
}
require.Len(t, seen, 1)
}
func TestProjectIdentityMapUsesPersistedFactsWithoutFilesystemInference(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("test uses POSIX-style temp git paths")
}
d := testDB(t)
ctx := context.Background()
persistedRoot := filepath.Join(t.TempDir(), "persisted")
liveRoot := filepath.Join(t.TempDir(), "live")
mappedRoot := filepath.Join(t.TempDir(), "mapped")
fileParentRoot := filepath.Join(t.TempDir(), "file-parent")
require.NoError(t, os.MkdirAll(filepath.Join(persistedRoot, ".git"), 0o755))
require.NoError(t, os.MkdirAll(filepath.Join(liveRoot, ".git"), 0o755))
require.NoError(t, os.MkdirAll(mappedRoot, 0o755))
require.NoError(t, os.MkdirAll(filepath.Join(fileParentRoot, ".git"), 0o755))
require.NoError(t, os.WriteFile(
filepath.Join(persistedRoot, ".git", "config"),
[]byte("[remote \"origin\"]\n\turl = https://github.com/live/wrong.git\n"),
0o644,
))
require.NoError(t, os.WriteFile(
filepath.Join(liveRoot, ".git", "config"),
[]byte("[remote \"origin\"]\n\turl = https://github.com/acme/live.git\n"),
0o644,
))
require.NoError(t, os.WriteFile(
filepath.Join(fileParentRoot, ".git", "config"),
[]byte("[remote \"origin\"]\n\turl = https://github.com/acme/file-parent.git\n"),
0o644,
))
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
Project: "persisted",
Machine: "laptop",
RootPath: persistedRoot,
GitRemote: "git@github.com:acme/persisted.git",
},
))
require.NoError(t, d.UpsertSession(Session{
ID: "persisted-session",
Project: "persisted",
Machine: "laptop",
Agent: "codex",
Cwd: persistedRoot,
}))
require.NoError(t, d.UpsertSession(Session{
ID: "live-session",
Project: "live",
Machine: "laptop",
Agent: "codex",
Cwd: liveRoot,
}))
require.NoError(t, d.UpsertSession(Session{
ID: "mapped-session",
Project: "mapped",
Machine: "laptop",
Agent: "codex",
Cwd: filepath.Join(mappedRoot, "feature"),
}))
fileParentPath := filepath.Join(fileParentRoot, "session.jsonl")
require.NoError(t, d.UpsertSession(Session{
ID: "file-parent-session",
Project: "file-parent",
Machine: "laptop",
Agent: "codex",
FilePath: &fileParentPath,
}))
_, err := d.CreateWorktreeProjectMapping(ctx, WorktreeProjectMapping{
Machine: "laptop",
PathPrefix: mappedRoot,
Project: "mapped",
Enabled: true,
})
require.NoError(t, err)
require.NoError(t, d.UpsertSession(Session{
ID: "remote-session",
Project: "remote",
Machine: "remote-host",
Agent: "codex",
Cwd: "remote-host:/srv/app",
}))
before := projectObservationRowCount(t, d)
got, err := d.BuildProjectIdentityMap(ctx,
[]string{"persisted", "live", "mapped", "file-parent", "remote", "unknown"},
)
require.NoError(t, err)
assert.Equal(t, before, projectObservationRowCount(t, d))
require.Equal(t, export.ProjectResolutionResolved, got["persisted"].Resolution)
require.NotNil(t, got["persisted"].Identity)
assert.Equal(t, "github.com/acme/persisted", got["persisted"].Identity.NormalizedRemote)
require.Equal(t, export.ProjectResolutionUnknown, got["live"].Resolution)
assert.Nil(t, got["live"].Identity)
require.Equal(t, export.ProjectResolutionUnknown, got["mapped"].Resolution)
assert.Nil(t, got["mapped"].Identity)
require.Equal(t, export.ProjectResolutionUnknown, got["file-parent"].Resolution)
assert.Nil(t, got["file-parent"].Identity)
assert.Equal(t, export.ProjectResolutionUnknown, got["remote"].Resolution)
assert.Nil(t, got["remote"].Identity)
assert.Equal(t, export.ProjectResolutionUnknown, got["unknown"].Resolution)
assert.Nil(t, got["unknown"].Identity)
}
func TestProjectIdentityMapLegacyFallbackAcceptsWindowsDriveRoots(t *testing.T) {
d := testDB(t)
ctx := context.Background()
require.NoError(t, d.UpsertSession(Session{
ID: "windows-backslash-session",
Project: "windows-backslash",
Machine: "windows-host",
Agent: "codex",
Cwd: `C:\repo\`,
}))
require.NoError(t, d.UpsertSession(Session{
ID: "windows-slash-session",
Project: "windows-slash",
Machine: "windows-host",
Agent: "codex",
Cwd: "C:/repo/",
}))
require.NoError(t, d.UpsertSession(Session{
ID: "remote-prefixed-session",
Project: "remote-prefixed",
Machine: "remote-host",
Agent: "codex",
Cwd: "host:/srv/repo",
}))
got, err := d.BuildProjectIdentityMap(ctx,
[]string{"windows-backslash", "windows-slash", "remote-prefixed"},
)
require.NoError(t, err)
require.Equal(t, export.ProjectResolutionUnknown, got["windows-backslash"].Resolution)
assert.Nil(t, got["windows-backslash"].Identity)
require.Equal(t, export.ProjectResolutionUnknown, got["windows-slash"].Resolution)
assert.Nil(t, got["windows-slash"].Identity)
assert.Equal(t, export.ProjectResolutionUnknown, got["remote-prefixed"].Resolution)
assert.Nil(t, got["remote-prefixed"].Identity)
}
func TestProjectIdentityMapUnknownPersistedObservationUsesLegacyFallback(t *testing.T) {
d := testDB(t)
ctx := context.Background()
root := filepath.Join(t.TempDir(), "fallback")
require.NoError(t, os.MkdirAll(filepath.Join(root, ".git"), 0o755))
require.NoError(t, d.UpsertProjectIdentityObservation(ctx,
export.ProjectIdentityObservation{
Project: "fallback",
Machine: "laptop",
RootPath: "remote-host:/srv/app",
ObservedAt: time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC),
}), "upsert unresolved persisted observation")
require.NoError(t, d.UpsertSession(Session{
ID: "fallback-session",
Project: "fallback",
Machine: "laptop",
Agent: "codex",
Cwd: root,
}))
got, err := d.BuildProjectIdentityMap(ctx, []string{"fallback"})
require.NoError(t, err)
require.Equal(t, export.ProjectResolutionUnknown,
got["fallback"].Resolution)
assert.Nil(t, got["fallback"].Identity)
}
func TestProjectIdentityMapLegacyFallbackUsesNoRemoteGitRoot(t *testing.T) {
d := testDB(t)
ctx := context.Background()
repo := t.TempDir()
require.NoError(t, os.Mkdir(filepath.Join(repo, ".git"), 0o755))
subdir := filepath.Join(repo, "pkg", "feature")
require.NoError(t, os.MkdirAll(subdir, 0o755))
require.NoError(t, d.UpsertSession(Session{
ID: "git-root-session",
Project: "git-root",
Machine: "laptop",
Agent: "codex",
Cwd: subdir,
}))
got, err := d.BuildProjectIdentityMap(ctx, []string{"git-root"})
require.NoError(t, err)
require.Equal(t, export.ProjectResolutionUnknown,
got["git-root"].Resolution)
assert.Nil(t, got["git-root"].Identity)
}
func TestProjectIdentityMapLegacyFallbackUsesRepoDotWorktreesMapping(t *testing.T) {
d := testDB(t)
ctx := context.Background()
parent := t.TempDir()
worktreesDir := filepath.Join(parent, "acme-app.worktrees")
_, err := d.CreateWorktreeProjectMapping(ctx, WorktreeProjectMapping{
Machine: "laptop",
PathPrefix: parent,
Layout: WorktreeMappingLayoutRepoDotWorktrees,
Enabled: true,
})
require.NoError(t, err)
require.NoError(t, d.UpsertSession(Session{
ID: "branch-a-session",
Project: "acme_app",
Machine: "laptop",
Agent: "codex",
Cwd: filepath.Join(worktreesDir, "branch-a"),
}))
require.NoError(t, d.UpsertSession(Session{
ID: "branch-b-session",
Project: "acme_app",
Machine: "laptop",
Agent: "codex",
Cwd: filepath.Join(worktreesDir, "branch-b"),
}))
got, err := d.BuildProjectIdentityMap(ctx, []string{"acme_app"})
require.NoError(t, err)
require.Equal(t, export.ProjectResolutionUnknown, got["acme_app"].Resolution)
assert.Nil(t, got["acme_app"].Identity)
}
func TestProjectIdentityMapLegacyFallbackClosesRowsBeforeMappingLookup(t *testing.T) {
d := testDB(t)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
reader := d.rawReader()
oldMaxOpen := reader.Stats().MaxOpenConnections
reader.SetMaxOpenConns(1)
t.Cleanup(func() { reader.SetMaxOpenConns(oldMaxOpen) })
root := t.TempDir()
_, err := d.CreateWorktreeProjectMapping(ctx,
WorktreeProjectMapping{
Machine: "laptop",
PathPrefix: root,
Project: "mapped",
Enabled: true,
})
require.NoError(t, err, "CreateWorktreeProjectMapping")
require.NoError(t, d.UpsertSession(Session{
ID: "mapped-session",
Project: "mapped",
Machine: "laptop",
Agent: "codex",
Cwd: filepath.Join(root, "pkg"),
}))
got, err := d.BuildProjectIdentityMap(ctx, []string{"mapped"})
require.NoError(t, err)
require.Equal(t, export.ProjectResolutionUnknown,
got["mapped"].Resolution)
assert.Nil(t, got["mapped"].Identity)
}
func projectObservationRowCount(t *testing.T, d *DB) int {
t.Helper()
var n int
require.NoError(t, d.getReader().QueryRow(
`SELECT COUNT(*) FROM project_identity_observations`,
).Scan(&n))
return n
}
func projectIdentitySHA(s string) string {
sum := sha256.Sum256([]byte(s))
return "sha256:" + hex.EncodeToString(sum[:])
}