Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions coderd/exp_chats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9085,6 +9085,43 @@ func TestRegenerateChatTitle(t *testing.T) {
require.Equal(t, "Test Chat", updated.Title)
})

t.Run("DoesNotBumpHistoryVersion", func(t *testing.T) {
t.Parallel()

ctx := testutil.Context(t, testutil.WaitLong)
client, db := newChatClientWithDatabase(t)
user := coderdtest.CreateFirstUser(t, client.Client)
modelConfig := createTitleGenerationModelConfig(t, client)

chat := dbgen.Chat(t, db, database.Chat{
OrganizationID: user.OrganizationID,
OwnerID: user.UserID,
LastModelConfigID: modelConfig.ID,
Title: "history fence chat",
})
seedManualTitleSourceMessage(t, db, chat, modelConfig.ID)

// Leave history_version lagging snapshot_version, as when a
// generation task is in flight. A chat_messages write here would
// sync it and break that task's commit fence.
_, err := db.LockChatAndBumpSnapshotVersion(dbauthz.AsSystemRestricted(ctx), chat.ID)
require.NoError(t, err)

before, err := db.GetChatByID(dbauthz.AsSystemRestricted(ctx), chat.ID)
require.NoError(t, err)
require.NotEqual(t, before.SnapshotVersion, before.HistoryVersion,
"setup must leave history_version lagging snapshot_version")

updated, err := client.RegenerateChatTitle(ctx, chat.ID)
require.NoError(t, err)
require.Equal(t, "Test Chat", updated.Title)

after, err := db.GetChatByID(dbauthz.AsSystemRestricted(ctx), chat.ID)
require.NoError(t, err)
require.Equal(t, before.HistoryVersion, after.HistoryVersion,
"manual title regeneration must not touch chat_messages")
})

t.Run("NoDefaultModelConfig", func(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -9256,6 +9293,41 @@ func TestProposeChatTitle(t *testing.T) {
require.True(t, persisted.UpdatedAt.Equal(before.UpdatedAt))
})

t.Run("DoesNotBumpHistoryVersion", func(t *testing.T) {
t.Parallel()

ctx := testutil.Context(t, testutil.WaitLong)
client, db := newChatClientWithDatabase(t)
user := coderdtest.CreateFirstUser(t, client.Client)
modelConfig := createTitleGenerationModelConfig(t, client)

chat := dbgen.Chat(t, db, database.Chat{
OrganizationID: user.OrganizationID,
OwnerID: user.UserID,
LastModelConfigID: modelConfig.ID,
Title: "history fence chat",
})
seedManualTitleSourceMessage(t, db, chat, modelConfig.ID)

// See the matching TestRegenerateChatTitle subtest.
_, err := db.LockChatAndBumpSnapshotVersion(dbauthz.AsSystemRestricted(ctx), chat.ID)
require.NoError(t, err)

before, err := db.GetChatByID(dbauthz.AsSystemRestricted(ctx), chat.ID)
require.NoError(t, err)
require.NotEqual(t, before.SnapshotVersion, before.HistoryVersion,
"setup must leave history_version lagging snapshot_version")

resp, err := client.ProposeChatTitle(ctx, chat.ID)
require.NoError(t, err)
require.Equal(t, "Test Chat", resp.Title)

after, err := db.GetChatByID(dbauthz.AsSystemRestricted(ctx), chat.ID)
require.NoError(t, err)
require.Equal(t, before.HistoryVersion, after.HistoryVersion,
"title proposal must not touch chat_messages")
})

t.Run("NoDefaultModelConfig", func(t *testing.T) {
t.Parallel()

Expand Down
2 changes: 1 addition & 1 deletion coderd/x/chatd/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ Each row in `chat_messages` has a `revision` column. It stores the `chats.snapsh

`chats.history_version` stores the latest `snapshot_version` in which chat message history changed. It starts at `0`, remains unchanged for non-history transitions, and is set to the current `snapshot_version` whenever a message is inserted or meaningfully updated. A newly created chat starts with `snapshot_version = 1`; because `Create` inserts initial history in that snapshot, the created chat's `history_version` becomes `1`. No-op message updates do not advance message `revision`, advance `history_version`, or reset `generation_attempt`. Whenever `history_version` changes, `generation_attempt` is reset to `0`; generation attempts are scoped to the current history version.

Message revision triggers depend on the transition invariant that `snapshot_version` is allocated immediately after the chat row is locked and before any message mutation happens. Runtime code must not assign `chat_messages.revision` directly.
Message revision triggers depend on the transition invariant that `snapshot_version` is allocated immediately after the chat row is locked and before any message mutation happens. Runtime code must not assign `chat_messages.revision` directly, and every `chat_messages` insert or update must go through a state machine transition: the triggers advance `history_version` on any write, so an out-of-band write (even of a hidden or soft-deleted row) moves `history_version` without a matching `snapshot_version` bump and breaks the fence of an in-flight generation task.

A `BEFORE INSERT` trigger assigns the current chat `snapshot_version` to the inserted message row and records the same value as the chat's latest history version:

Expand Down
Loading
Loading