From f29a4d77d24c35239e370a5ba58d008b64713d3e Mon Sep 17 00:00:00 2001 From: Daniel Rosales <111561081+dnlrsls@users.noreply.github.com> Date: Sat, 26 Sep 2026 12:45:43 -0500 Subject: [PATCH 1/6] feat(store): deduplicate admitted prompts by inbox identity --- docs/ARCHITECTURE.md | 2 + internal/server/server.go | 6 ++- internal/server/server_test.go | 29 ++++++++++ internal/store/store.go | 46 ++++++++++++---- internal/store/store_test.go | 98 ++++++++++++++++++++++++++++++++++ 5 files changed, 168 insertions(+), 13 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 8884cb4e9..debfdc0d3 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -2,6 +2,8 @@ # Architecture +Local `POST /prompts` accepts optional `source_inbox_id` alongside `session_id`, `content`, and `project`. A nonempty ID identifies one prompt within its session: replay returns the existing prompt ID with the same `201` and `{"id":…, "status":"saved"}` response, without another sync mutation or write notification. Distinct IDs may contain identical text. Omitting the ID continues to append a new prompt on every call. Project ownership checks still apply before replay. Sync/import propagation and replay after deletion are not yet supported. + - [How It Works](#how-it-works) - [Session Lifecycle](#session-lifecycle) - [MCP Tools](#mcp-tools) diff --git a/internal/server/server.go b/internal/server/server.go index 4afaca9ff..ceeed95db 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -1051,7 +1051,7 @@ func (s *Server) handleAddPrompt(w http.ResponseWriter, r *http.Request) { } } - id, err := s.store.AddPrompt(body) + id, inserted, err := s.store.AddPromptWithResult(body) if err != nil { switch { case errors.Is(err, store.ErrPromptContentRequired): @@ -1063,7 +1063,9 @@ func (s *Server) handleAddPrompt(w http.ResponseWriter, r *http.Request) { return } - s.notifyWrite() + if inserted { + s.notifyWrite() + } jsonResponse(w, http.StatusCreated, map[string]any{"id": id, "status": "saved"}) } diff --git a/internal/server/server_test.go b/internal/server/server_test.go index a69bfa5d4..4ca9eb68b 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -2088,6 +2088,35 @@ func TestSyncStatusResolvesProjectSelectors(t *testing.T) { } } +func TestPromptInboxIdentityHTTP(t *testing.T) { + st := newServerTestStore(t) + srv := New(st, 0) + h := srv.Handler() + var writes atomic.Int32 + srv.SetOnWrite(func() { writes.Add(1) }) + if err := st.CreateSession("inbox-http", "engram", "/tmp"); err != nil { + t.Fatal(err) + } + request := func(body string) (int, string) { + rec := httptest.NewRecorder() + h.ServeHTTP(rec, httptest.NewRequest(http.MethodPost, "/prompts", strings.NewReader(body))) + return rec.Code, rec.Body.String() + } + payload := `{"session_id":"inbox-http","project":"engram","content":"same","source_inbox_id":"a"}` + code, first := request(payload) + if code != http.StatusCreated || writes.Load() != 1 { + t.Fatalf("first: %d %s writes=%d", code, first, writes.Load()) + } + code, replay := request(payload) + if code != http.StatusCreated || replay != first || writes.Load() != 1 { + t.Fatalf("replay: %d %s writes=%d", code, replay, writes.Load()) + } + code, _ = request(`{"session_id":"inbox-http","project":"other","content":"same","source_inbox_id":"a"}`) + if code == http.StatusCreated || writes.Load() != 1 { + t.Fatalf("wrong project: %d writes=%d", code, writes.Load()) + } +} + // ─── OnWrite Notification Tests ────────────────────────────────────────────── func TestOnWriteCalledAfterSuccessfulWrites(t *testing.T) { diff --git a/internal/store/store.go b/internal/store/store.go index e97ab5120..977b20844 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -301,9 +301,10 @@ type Prompt struct { } type AddPromptParams struct { - SessionID string `json:"session_id"` - Content string `json:"content"` - Project string `json:"project,omitempty"` + SessionID string `json:"session_id"` + Content string `json:"content"` + Project string `json:"project,omitempty"` + SourceInboxID string `json:"source_inbox_id,omitempty"` } // TruncationMetadata describes storage content processing after private-tag redaction. @@ -1234,6 +1235,7 @@ func (s *Store) migrate() error { CREATE TABLE IF NOT EXISTS user_prompts ( id INTEGER PRIMARY KEY AUTOINCREMENT, sync_id TEXT, + source_inbox_id TEXT, session_id TEXT NOT NULL, content TEXT NOT NULL, project TEXT, @@ -1383,6 +1385,9 @@ func (s *Store) migrate() error { if err := s.addColumnIfNotExists("user_prompts", "sync_id", "TEXT"); err != nil { return err } + if err := s.addColumnIfNotExists("user_prompts", "source_inbox_id", "TEXT"); err != nil { + return err + } if err := s.addColumnIfNotExists("sync_delete_tombstones", "last_remote_mutation_seq", "INTEGER"); err != nil { return err } @@ -1398,6 +1403,7 @@ func (s *Store) migrate() error { CREATE INDEX IF NOT EXISTS idx_obs_deleted ON observations(deleted_at); CREATE INDEX IF NOT EXISTS idx_obs_dedupe ON observations(normalized_hash, project, scope, type, title, created_at DESC); CREATE INDEX IF NOT EXISTS idx_prompts_sync_id ON user_prompts(sync_id); + CREATE UNIQUE INDEX IF NOT EXISTS idx_prompts_source_inbox ON user_prompts(session_id, source_inbox_id) WHERE source_inbox_id IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_prompt_tombstones_project ON prompt_tombstones(project, deleted_at DESC); CREATE INDEX IF NOT EXISTS idx_sync_delete_tombstones_project ON sync_delete_tombstones(project, deleted_at DESC); CREATE INDEX IF NOT EXISTS idx_sync_mutations_target_seq ON sync_mutations(target_key, seq); @@ -3767,15 +3773,21 @@ func (s *Store) markReviewed(id int64, project string) error { // ─── User Prompts ──────────────────────────────────────────────────────────── func (s *Store) AddPrompt(p AddPromptParams) (int64, error) { - // Normalize project name before storing + id, _, err := s.AddPromptWithResult(p) + return id, err +} + +// AddPromptWithResult reports whether a prompt was inserted rather than replayed. +func (s *Store) AddPromptWithResult(p AddPromptParams) (int64, bool, error) { p.Project, _ = NormalizeProject(p.Project) content, _ := s.prepareStoredContent(p.Content) if content == "" { - return 0, ErrPromptContentRequired + return 0, false, ErrPromptContentRequired } var promptID int64 + inserted := false err := s.withTx(func(tx *sql.Tx) error { { // Settle ownership first: an unowned legacy session adopts this @@ -3787,17 +3799,29 @@ func (s *Store) AddPrompt(p AddPromptParams) (int64, error) { p.Project = resolved } syncID := newSyncID("prompt") - res, err := s.execHook(tx, - `INSERT INTO user_prompts (sync_id, session_id, content, project) VALUES (?, ?, ?, ?)`, - syncID, p.SessionID, content, nullableString(p.Project), - ) + query := `INSERT INTO user_prompts (sync_id, session_id, content, project, source_inbox_id) VALUES (?, ?, ?, ?, ?)` + if p.SourceInboxID != "" { + query += ` ON CONFLICT(session_id, source_inbox_id) WHERE source_inbox_id IS NOT NULL DO NOTHING` + } + res, err := s.execHook(tx, query, + syncID, p.SessionID, content, nullableString(p.Project), nullableString(p.SourceInboxID)) if err != nil { return err } + if p.SourceInboxID != "" { + affected, err := res.RowsAffected() + if err != nil { + return err + } + if affected == 0 { + return tx.QueryRow(`SELECT id FROM user_prompts WHERE session_id = ? AND source_inbox_id = ?`, p.SessionID, p.SourceInboxID).Scan(&promptID) + } + } promptID, err = res.LastInsertId() if err != nil { return err } + inserted = true var createdAt string if err := tx.QueryRow(`SELECT created_at FROM user_prompts WHERE id = ?`, promptID).Scan(&createdAt); err != nil { return err @@ -3814,9 +3838,9 @@ func (s *Store) AddPrompt(p AddPromptParams) (int64, error) { }) }) if err != nil { - return 0, err + return 0, false, err } - return promptID, nil + return promptID, inserted, nil } func (s *Store) AddPromptIfMissing(p AddPromptParams) (int64, bool, error) { diff --git a/internal/store/store_test.go b/internal/store/store_test.go index ab2850166..69634f019 100644 --- a/internal/store/store_test.go +++ b/internal/store/store_test.go @@ -765,6 +765,104 @@ func TestAddObservationAdoptsUnownedLegacySessionProject(t *testing.T) { } } +func TestPromptInboxIdentityStore(t *testing.T) { + s := newTestStore(t) + if err := s.CreateSession("inbox-session", "engram", "/tmp"); err != nil { + t.Fatal(err) + } + p := AddPromptParams{SessionID: "inbox-session", Project: "engram", Content: "same", SourceInboxID: "inbox-1"} + first, inserted, err := s.AddPromptWithResult(p) + if err != nil || !inserted { + t.Fatalf("first: %d %v %v", first, inserted, err) + } + var before int + if err := s.DB().QueryRow(`SELECT count(*) FROM sync_mutations`).Scan(&before); err != nil { + t.Fatal(err) + } + again, inserted, err := s.AddPromptWithResult(p) + if err != nil || inserted || again != first { + t.Fatalf("replay: %d %v %v", again, inserted, err) + } + var after int + if err := s.DB().QueryRow(`SELECT count(*) FROM sync_mutations`).Scan(&after); err != nil || after != before { + t.Fatalf("mutations: %d -> %d: %v", before, after, err) + } + if err := s.CreateSession("other-session", "engram", "/tmp"); err != nil { + t.Fatal(err) + } + other := p + other.SessionID = "other-session" + otherID, otherInserted, err := s.AddPromptWithResult(other) + if err != nil || !otherInserted || otherID == first { + t.Fatalf("same inbox ID in another session: %d %v %v", otherID, otherInserted, err) + } + p.SourceInboxID = "inbox-2" + second, inserted, err := s.AddPromptWithResult(p) + if err != nil || !inserted || second == first { + t.Fatalf("distinct: %d %v %v", second, inserted, err) + } + p.SourceInboxID = "" + third, err := s.AddPrompt(p) + if err != nil { + t.Fatal(err) + } + fourth, err := s.AddPrompt(p) + if err != nil || fourth == third { + t.Fatalf("legacy: %d %d %v", third, fourth, err) + } +} + +func TestPromptInboxIdentityStoreConcurrentReplay(t *testing.T) { + s := newTestStore(t) + if err := s.CreateSession("concurrent-inbox", "engram", "/tmp"); err != nil { + t.Fatal(err) + } + enrollTestProject(t, s, "engram") + const workers = 12 + start := make(chan struct{}) + type result struct { + id int64 + inserted bool + err error + } + results := make(chan result, workers) + p := AddPromptParams{SessionID: "concurrent-inbox", Project: "engram", Content: "same", SourceInboxID: "shared"} + for i := 0; i < workers; i++ { + go func() { + <-start + id, inserted, err := s.AddPromptWithResult(p) + results <- result{id, inserted, err} + }() + } + close(start) + var first int64 + var inserts int + for i := 0; i < workers; i++ { + r := <-results + if r.err != nil { + t.Fatal(r.err) + } + if first == 0 { + first = r.id + } else if r.id != first { + t.Fatalf("concurrent replay returned %d, want %d", r.id, first) + } + if r.inserted { + inserts++ + } + } + if inserts != 1 { + t.Fatalf("inserted %d times, want one", inserts) + } + var mutations int + if err := s.DB().QueryRow(`SELECT count(*) FROM sync_mutations WHERE entity = 'prompt'`).Scan(&mutations); err != nil { + t.Fatal(err) + } + if mutations != 1 { + t.Fatalf("prompt sync mutations = %d, want one", mutations) + } +} + func TestAddPromptAdoptsUnownedLegacySessionProject(t *testing.T) { type legacySession struct{ id, project string } s := newTestStoreWithNullableLegacySessions(t, legacySession{"null-session", ""}) From 5a92b843d3ffc2022c32c20b522572455dba3a92 Mon Sep 17 00:00:00 2001 From: Daniel Rosales <111561081+dnlrsls@users.noreply.github.com> Date: Sat, 26 Sep 2026 17:19:44 -0500 Subject: [PATCH 2/6] test(server): require precise wrong-project prompt rejection --- internal/server/server_test.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/internal/server/server_test.go b/internal/server/server_test.go index 4ca9eb68b..7090d17b9 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -2111,9 +2111,14 @@ func TestPromptInboxIdentityHTTP(t *testing.T) { if code != http.StatusCreated || replay != first || writes.Load() != 1 { t.Fatalf("replay: %d %s writes=%d", code, replay, writes.Load()) } - code, _ = request(`{"session_id":"inbox-http","project":"other","content":"same","source_inbox_id":"a"}`) - if code == http.StatusCreated || writes.Load() != 1 { - t.Fatalf("wrong project: %d writes=%d", code, writes.Load()) + code, rejected := request(`{"session_id":"inbox-http","project":"other","content":"same","source_inbox_id":"a"}`) + var rejection map[string]string + if err := json.Unmarshal([]byte(rejected), &rejection); err != nil { + t.Fatalf("decode wrong-project response: %v", err) + } + if code != http.StatusBadRequest || rejection["code"] != "session_project_mismatch" || + rejection["error"] != "session project does not match requested project" || writes.Load() != 1 { + t.Fatalf("wrong project: %d %s writes=%d", code, rejected, writes.Load()) } } From b44a88f771c6020bacd2b425e9bc3443ca7f095b Mon Sep 17 00:00:00 2001 From: Daniel Rosales <111561081+dnlrsls@users.noreply.github.com> Date: Sat, 26 Sep 2026 21:33:35 -0500 Subject: [PATCH 3/6] test(server): assert initial prompt inbox response identity --- internal/server/server_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/internal/server/server_test.go b/internal/server/server_test.go index 7090d17b9..880ba4880 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -2107,6 +2107,16 @@ func TestPromptInboxIdentityHTTP(t *testing.T) { if code != http.StatusCreated || writes.Load() != 1 { t.Fatalf("first: %d %s writes=%d", code, first, writes.Load()) } + var created struct { + ID int64 `json:"id"` + Status string `json:"status"` + } + if err := json.Unmarshal([]byte(first), &created); err != nil { + t.Fatalf("decode first response: %v", err) + } + if created.ID <= 0 || created.Status != "saved" { + t.Fatalf("first response: id=%d status=%q body=%s", created.ID, created.Status, first) + } code, replay := request(payload) if code != http.StatusCreated || replay != first || writes.Load() != 1 { t.Fatalf("replay: %d %s writes=%d", code, replay, writes.Load()) From 2c27fd4baac289631df224f6281aa7cd7d872b95 Mon Sep 17 00:00:00 2001 From: Daniel Rosales <111561081+dnlrsls@users.noreply.github.com> Date: Sat, 26 Sep 2026 23:44:02 -0500 Subject: [PATCH 4/6] test(store): cover legacy prompt inbox migration --- internal/store/store_test.go | 61 +++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/internal/store/store_test.go b/internal/store/store_test.go index 69634f019..9ad9e81b7 100644 --- a/internal/store/store_test.go +++ b/internal/store/store_test.go @@ -2734,15 +2734,17 @@ func TestNewMigratesLegacyUserPromptsSyncIDSchema(t *testing.T) { } t.Cleanup(func() { _ = s.Close() }) - var syncID string - if err := s.db.QueryRow("SELECT sync_id FROM user_prompts WHERE content = ?", "legacy prompt").Scan(&syncID); err != nil { - t.Fatalf("query migrated prompt sync_id: %v", err) + var legacyID int64 + var legacyContent, syncID string + var legacyInboxID sql.NullString + if err := s.db.QueryRow("SELECT id, content, sync_id, source_inbox_id FROM user_prompts WHERE session_id = ?", "s1").Scan(&legacyID, &legacyContent, &syncID, &legacyInboxID); err != nil { + t.Fatalf("query migrated legacy prompt: %v", err) } - if syncID == "" { - t.Fatalf("expected migrated prompt sync_id to be backfilled") + if legacyID != 1 || legacyContent != "legacy prompt" || syncID == "" || legacyInboxID.Valid { + t.Fatalf("legacy prompt not preserved: id=%d content=%q sync_id=%q inbox_id=%v", legacyID, legacyContent, syncID, legacyInboxID) } - var hasSyncIDColumn bool + var hasSyncIDColumn, hasInboxIDColumn bool rows, err := s.db.Query("PRAGMA table_info(user_prompts)") if err != nil { t.Fatalf("query prompt columns: %v", err) @@ -2755,20 +2757,24 @@ func TestNewMigratesLegacyUserPromptsSyncIDSchema(t *testing.T) { if err := rows.Scan(&cid, &name, &columnType, ¬Null, &defaultValue, &pk); err != nil { t.Fatalf("scan prompt column: %v", err) } - if name == "sync_id" { + switch name { + case "sync_id": hasSyncIDColumn = true - break + case "source_inbox_id": + hasInboxIDColumn = true } } if err := rows.Err(); err != nil { - rows.Close() + if closeErr := rows.Close(); closeErr != nil { + t.Fatalf("iterate prompt columns: %v; close prompt columns: %v", err, closeErr) + } t.Fatalf("iterate prompt columns: %v", err) } if err := rows.Close(); err != nil { t.Fatalf("close prompt columns: %v", err) } - if !hasSyncIDColumn { - t.Fatalf("expected user_prompts.sync_id column after migration") + if !hasSyncIDColumn || !hasInboxIDColumn { + t.Fatalf("expected sync_id and source_inbox_id columns after migration: sync=%v inbox=%v", hasSyncIDColumn, hasInboxIDColumn) } var indexName string @@ -2778,6 +2784,39 @@ func TestNewMigratesLegacyUserPromptsSyncIDSchema(t *testing.T) { if indexName != "idx_prompts_sync_id" { t.Fatalf("expected idx_prompts_sync_id to exist, got %q", indexName) } + + var indexSQL string + if err := s.db.QueryRow("SELECT sql FROM sqlite_master WHERE type = 'index' AND name = 'idx_prompts_source_inbox' AND tbl_name = 'user_prompts'").Scan(&indexSQL); err != nil { + t.Fatalf("query prompt inbox index: %v", err) + } + if !strings.Contains(indexSQL, "UNIQUE INDEX") || !strings.Contains(indexSQL, "(session_id, source_inbox_id)") || !strings.Contains(indexSQL, "WHERE source_inbox_id IS NOT NULL") { + t.Fatalf("expected unique partial session/inbox index, got %q", indexSQL) + } + + params := AddPromptParams{SessionID: "s1", Content: "new prompt", Project: "engram", SourceInboxID: "inbox-1"} + promptID, inserted, err := s.AddPromptWithResult(params) + if err != nil || !inserted || promptID <= 0 || promptID == legacyID { + t.Fatalf("insert inbox prompt: id=%d inserted=%v err=%v", promptID, inserted, err) + } + params.Content = "replayed prompt must not replace original" + replayID, inserted, err := s.AddPromptWithResult(params) + if err != nil || inserted || replayID != promptID { + t.Fatalf("replay inbox prompt: id=%d inserted=%v err=%v; original id=%d", replayID, inserted, err, promptID) + } + var count int + if err := s.db.QueryRow("SELECT COUNT(*) FROM user_prompts WHERE session_id = ?", "s1").Scan(&count); err != nil { + t.Fatalf("count prompts after replay: %v", err) + } + if count != 2 { + t.Fatalf("expected legacy and new prompt only, got %d", count) + } + var storedContent, storedSyncID string + if err := s.db.QueryRow("SELECT content, sync_id FROM user_prompts WHERE id = ?", legacyID).Scan(&storedContent, &storedSyncID); err != nil { + t.Fatalf("query legacy prompt after replay: %v", err) + } + if storedContent != legacyContent || storedSyncID != syncID { + t.Fatalf("legacy prompt changed after replay: content=%q sync_id=%q", storedContent, storedSyncID) + } } func TestSuggestTopicKeyNormalizesDeterministically(t *testing.T) { From c1a44253469fae5afc4dcb56b459cf75a971e7a5 Mon Sep 17 00:00:00 2001 From: Daniel Rosales <111561081+dnlrsls@users.noreply.github.com> Date: Sun, 27 Sep 2026 01:00:57 -0500 Subject: [PATCH 5/6] fix(store): reset prompt insert result on retry --- internal/store/store.go | 1 + internal/store/store_test.go | 56 ++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/internal/store/store.go b/internal/store/store.go index 977b20844..de223181e 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -3789,6 +3789,7 @@ func (s *Store) AddPromptWithResult(p AddPromptParams) (int64, bool, error) { var promptID int64 inserted := false err := s.withTx(func(tx *sql.Tx) error { + inserted = false { // Settle ownership first: an unowned legacy session adopts this // write's project rather than rejecting the write forever. diff --git a/internal/store/store_test.go b/internal/store/store_test.go index 9ad9e81b7..60e00f653 100644 --- a/internal/store/store_test.go +++ b/internal/store/store_test.go @@ -812,6 +812,62 @@ func TestPromptInboxIdentityStore(t *testing.T) { } } +func TestPromptInboxIdentityStoreRetryReplaysCompetingWrite(t *testing.T) { + s := newTestStore(t) + if err := s.CreateSession("retry-inbox", "engram", "/tmp"); err != nil { + t.Fatal(err) + } + p := AddPromptParams{SessionID: "retry-inbox", Project: "engram", Content: "attempt", SourceInboxID: "inbox-1"} + competing, err := sql.Open("sqlite", storeDSN(filepath.Join(s.cfg.DataDir, "engram.db"))) + if err != nil { + t.Fatal(err) + } + defer competing.Close() + + originalCommit := s.hooks.commit + attempts := 0 + var competingID int64 + s.hooks.commit = func(tx *sql.Tx) error { + attempts++ + if attempts != 1 { + return originalCommit(tx) + } + var pending int + if err := tx.QueryRow(`SELECT count(*) FROM user_prompts WHERE session_id = ? AND source_inbox_id = ?`, p.SessionID, p.SourceInboxID).Scan(&pending); err != nil { + return err + } + if pending != 1 { + t.Fatalf("first attempt prompt count = %d, want 1", pending) + } + if err := tx.Rollback(); err != nil { + return err + } + res, err := competing.ExecContext(context.Background(), + `INSERT INTO user_prompts (sync_id, session_id, content, project, source_inbox_id) VALUES (?, ?, ?, ?, ?)`, + "competing-prompt", p.SessionID, "competing", p.Project, p.SourceInboxID) + if err != nil { + return err + } + competingID, err = res.LastInsertId() + if err != nil { + return err + } + return errors.New("database is locked") + } + t.Cleanup(func() { s.hooks.commit = originalCommit }) + + id, inserted, err := s.AddPromptWithResult(p) + if err != nil { + t.Fatal(err) + } + if attempts != 2 || id != competingID || inserted { + t.Fatalf("attempts=%d id=%d competingID=%d inserted=%t; want two attempts, competing ID and false", attempts, id, competingID, inserted) + } + if got := scalarInt(t, s, `SELECT count(*) FROM user_prompts WHERE session_id='retry-inbox' AND source_inbox_id='inbox-1'`); got != 1 { + t.Fatalf("persisted prompts = %d, want 1", got) + } +} + func TestPromptInboxIdentityStoreConcurrentReplay(t *testing.T) { s := newTestStore(t) if err := s.CreateSession("concurrent-inbox", "engram", "/tmp"); err != nil { From be4157d95f63e0282bf1e2b0abd1f7f26f6fc660 Mon Sep 17 00:00:00 2001 From: Daniel Rosales <111561081+dnlrsls@users.noreply.github.com> Date: Sun, 27 Sep 2026 01:23:40 -0500 Subject: [PATCH 6/6] test(store): check competing prompt DB close error --- internal/store/store_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/store/store_test.go b/internal/store/store_test.go index 60e00f653..6f47d155d 100644 --- a/internal/store/store_test.go +++ b/internal/store/store_test.go @@ -822,7 +822,11 @@ func TestPromptInboxIdentityStoreRetryReplaysCompetingWrite(t *testing.T) { if err != nil { t.Fatal(err) } - defer competing.Close() + defer func() { + if err := competing.Close(); err != nil { + t.Errorf("close competing prompt database: %v", err) + } + }() originalCommit := s.hooks.commit attempts := 0