Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
242 lines
9.1 KiB
Go
242 lines
9.1 KiB
Go
package storesqlite
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestListWorkspaceGeneratedFileTurnsScopesAndOrdersSettledTurns(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
store := openTestStore(t, testOptions(&staticProjectPaths{paths: []string{
|
|
"/workspace/project-a",
|
|
"/workspace/project-b",
|
|
}}))
|
|
ctx := context.Background()
|
|
const workspaceID = "workspace-generated-files"
|
|
seedGeneratedFileSession(t, ctx, store, workspaceID, "older", testTargetIDCodex, "/workspace/project-a/apps/web")
|
|
seedGeneratedFileSession(t, ctx, store, workspaceID, "newer", testTargetIDClaude, "/workspace/project-a")
|
|
seedGeneratedFileSession(t, ctx, store, workspaceID, "other-project", testTargetIDCodex, "/workspace/project-b")
|
|
|
|
recordGeneratedFileTurn(t, ctx, store, workspaceID, "older", "turn-older", 100, []any{
|
|
map[string]any{"path": "src/report.md", "change": "added"},
|
|
})
|
|
recordGeneratedFileTurn(t, ctx, store, workspaceID, "newer", "turn-newer", 200, []any{
|
|
map[string]any{"path": "report.md", "change": "deleted"},
|
|
})
|
|
recordGeneratedFileTurn(t, ctx, store, workspaceID, "other-project", "turn-other", 300, []any{
|
|
map[string]any{"path": "other.md", "change": "added"},
|
|
})
|
|
if _, accepted, err := store.RecordTurnTransition(ctx, TurnTransition{
|
|
WorkspaceID: workspaceID, AgentSessionID: "newer", TurnID: "turn-active",
|
|
Phase: TurnPhaseRunning, Origin: TurnOriginLegacyUnknown, OccurredAtUnixMS: 400,
|
|
}); err != nil || !accepted {
|
|
t.Fatalf("RecordTurnTransition(active) accepted=%v error=%v", accepted, err)
|
|
}
|
|
|
|
result, ok, err := store.ListWorkspaceGeneratedFileTurns(ctx, ListWorkspaceGeneratedFileTurnsInput{
|
|
WorkspaceID: workspaceID,
|
|
SectionKey: RailSectionKeyForProject("/workspace/project-a"),
|
|
})
|
|
if err != nil || !ok {
|
|
t.Fatalf("ListWorkspaceGeneratedFileTurns() ok=%v error=%v", ok, err)
|
|
}
|
|
if len(result.Turns) != 2 {
|
|
t.Fatalf("turns = %#v, want two settled project-a turns", result.Turns)
|
|
}
|
|
if result.Turns[0].TurnID != "turn-newer" || result.Turns[1].TurnID != "turn-older" {
|
|
t.Fatalf("turn order = %q, %q", result.Turns[0].TurnID, result.Turns[1].TurnID)
|
|
}
|
|
if result.Turns[0].AgentTargetID != testTargetIDClaude || len(result.Turns[0].Changes) != 1 || result.Turns[0].Changes[0].Change != "deleted" {
|
|
t.Fatalf("newer turn = %#v", result.Turns[0])
|
|
}
|
|
}
|
|
|
|
func TestListWorkspaceGeneratedFileTurnsKeepsRetractedTurnSideEffects(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
store := openTestStore(t, testOptions(&staticProjectPaths{paths: []string{"/workspace/project"}}))
|
|
ctx := context.Background()
|
|
const workspaceID = "workspace-retracted-generated-files"
|
|
const sessionID = "session-1"
|
|
const turnID = "turn-retracted"
|
|
seedGeneratedFileSession(t, ctx, store, workspaceID, sessionID, testTargetIDCodex, "/workspace/project")
|
|
recordGeneratedFileTurn(t, ctx, store, workspaceID, sessionID, turnID, 100, []any{
|
|
map[string]any{"path": "still-modified.md", "change": "added"},
|
|
})
|
|
if _, err := store.db.ExecContext(ctx, `
|
|
UPDATE workspace_agent_turn_history
|
|
SET history_state = 'retracted', retracted_by_operation_id = 'operation-1'
|
|
WHERE workspace_id = ? AND agent_session_id = ? AND turn_id = ?
|
|
`, workspaceID, sessionID, turnID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
result, ok, err := store.ListWorkspaceGeneratedFileTurns(ctx, ListWorkspaceGeneratedFileTurnsInput{
|
|
WorkspaceID: workspaceID,
|
|
SectionKey: RailSectionKeyForProject("/workspace/project"),
|
|
})
|
|
if err != nil || !ok {
|
|
t.Fatalf("ListWorkspaceGeneratedFileTurns() ok=%v error=%v", ok, err)
|
|
}
|
|
if len(result.Turns) != 1 ||
|
|
result.Turns[0].TurnID != turnID ||
|
|
len(result.Turns[0].Changes) != 1 ||
|
|
result.Turns[0].Changes[0].Path != "still-modified.md" {
|
|
t.Fatalf("retracted turn side effects = %#v", result.Turns)
|
|
}
|
|
}
|
|
|
|
func TestListWorkspaceGeneratedFileTurnsCapsSectionAtOneHundred(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
store := openTestStore(t, testOptions(&staticProjectPaths{paths: []string{"/workspace/project"}}))
|
|
ctx := context.Background()
|
|
const workspaceID = "workspace-generated-file-limit"
|
|
const sessionID = "session-1"
|
|
seedGeneratedFileSession(t, ctx, store, workspaceID, sessionID, testTargetIDCodex, "/workspace/project")
|
|
for index := 0; index < sectionGeneratedFileTurnLimit+1; index++ {
|
|
recordGeneratedFileTurn(t, ctx, store, workspaceID, sessionID, fmt.Sprintf("turn-%03d", index), int64(100+index), []any{
|
|
map[string]any{"path": fmt.Sprintf("file-%03d.md", index), "change": "added"},
|
|
})
|
|
}
|
|
result, ok, err := store.ListWorkspaceGeneratedFileTurns(ctx, ListWorkspaceGeneratedFileTurnsInput{
|
|
WorkspaceID: workspaceID,
|
|
SectionKey: RailSectionKeyForProject("/workspace/project"),
|
|
})
|
|
if err != nil || !ok {
|
|
t.Fatalf("ListWorkspaceGeneratedFileTurns() ok=%v error=%v", ok, err)
|
|
}
|
|
if len(result.Turns) != sectionGeneratedFileTurnLimit || result.Turns[0].TurnID != "turn-100" || result.Turns[len(result.Turns)-1].TurnID != "turn-001" {
|
|
t.Fatalf("bounded turns = %#v", result.Turns)
|
|
}
|
|
}
|
|
|
|
func TestListWorkspaceGeneratedFileTurnsAllowsSparseQuietSections(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
store := openTestStore(t, testOptions(&staticProjectPaths{paths: []string{
|
|
"/workspace/quiet",
|
|
"/workspace/noisy",
|
|
}}))
|
|
ctx := context.Background()
|
|
const workspaceID = "workspace-generated-file-sparse"
|
|
seedGeneratedFileSession(t, ctx, store, workspaceID, "quiet", testTargetIDCodex, "/workspace/quiet")
|
|
seedGeneratedFileSession(t, ctx, store, workspaceID, "noisy", testTargetIDCodex, "/workspace/noisy")
|
|
recordGeneratedFileTurn(t, ctx, store, workspaceID, "quiet", "turn-quiet", 100, []any{
|
|
map[string]any{"path": "quiet.md", "change": "added"},
|
|
})
|
|
for index := 0; index < workspaceGeneratedFileTurnCandidateLimit; index++ {
|
|
recordGeneratedFileTurn(t, ctx, store, workspaceID, "noisy", fmt.Sprintf("turn-noisy-%04d", index), int64(1000+index), nil)
|
|
}
|
|
result, ok, err := store.ListWorkspaceGeneratedFileTurns(ctx, ListWorkspaceGeneratedFileTurnsInput{
|
|
WorkspaceID: workspaceID,
|
|
SectionKey: RailSectionKeyForProject("/workspace/quiet"),
|
|
})
|
|
if err != nil || !ok {
|
|
t.Fatalf("ListWorkspaceGeneratedFileTurns() ok=%v error=%v", ok, err)
|
|
}
|
|
if len(result.Turns) != 0 {
|
|
t.Fatalf("quiet section turns = %#v, want sparse empty result outside candidate window", result.Turns)
|
|
}
|
|
}
|
|
|
|
func TestListWorkspaceGeneratedFileTurnsPlanUsesRecentSettledIndex(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
store := openTestStore(t, testOptions(&staticProjectPaths{}))
|
|
rows, err := store.db.Query(`
|
|
EXPLAIN QUERY PLAN
|
|
WITH recent_workspace_turns AS MATERIALIZED (
|
|
SELECT agent_session_id, turn_id, settled_at_unix_ms
|
|
FROM workspace_agent_turns INDEXED BY idx_workspace_agent_turns_workspace_settled_recent
|
|
WHERE workspace_id = ? AND phase = 'settled' AND settled_at_unix_ms IS NOT NULL
|
|
ORDER BY settled_at_unix_ms DESC, agent_session_id DESC, turn_id DESC
|
|
LIMIT ?
|
|
),
|
|
scoped_turns AS MATERIALIZED (
|
|
SELECT turns.agent_session_id, turns.turn_id, turns.settled_at_unix_ms
|
|
FROM recent_workspace_turns turns
|
|
JOIN workspace_agent_sessions sessions
|
|
ON sessions.workspace_id = ? AND sessions.agent_session_id = turns.agent_session_id
|
|
WHERE sessions.rail_section_key = ? AND sessions.deleted_at_unix_ms = 0
|
|
ORDER BY turns.settled_at_unix_ms DESC, turns.agent_session_id DESC, turns.turn_id DESC
|
|
LIMIT ?
|
|
)
|
|
SELECT source.file_changes_json
|
|
FROM scoped_turns scoped
|
|
JOIN workspace_agent_turns source
|
|
ON source.workspace_id = ?
|
|
AND source.agent_session_id = scoped.agent_session_id
|
|
AND source.turn_id = scoped.turn_id
|
|
`, "workspace", workspaceGeneratedFileTurnCandidateLimit, "workspace", RailSectionKeyConversations, sectionGeneratedFileTurnLimit, "workspace")
|
|
if err != nil {
|
|
t.Fatalf("EXPLAIN QUERY PLAN error = %v", err)
|
|
}
|
|
defer rows.Close()
|
|
var plan strings.Builder
|
|
for rows.Next() {
|
|
var id, parent, unused int
|
|
var detail string
|
|
if err := rows.Scan(&id, &parent, &unused, &detail); err != nil {
|
|
t.Fatalf("scan query plan: %v", err)
|
|
}
|
|
plan.WriteString(detail)
|
|
plan.WriteByte('\n')
|
|
}
|
|
if !strings.Contains(plan.String(), "idx_workspace_agent_turns_workspace_settled_recent") {
|
|
t.Fatalf("query plan does not use settled index:\n%s", plan.String())
|
|
}
|
|
}
|
|
|
|
func seedGeneratedFileSession(
|
|
t *testing.T,
|
|
ctx context.Context,
|
|
store *Store,
|
|
workspaceID string,
|
|
sessionID string,
|
|
agentTargetID string,
|
|
cwd string,
|
|
) {
|
|
t.Helper()
|
|
if _, err := store.ReportSessionState(ctx, SessionStateReport{
|
|
WorkspaceID: workspaceID,
|
|
AgentSessionID: sessionID,
|
|
Origin: "runtime",
|
|
AgentTargetID: agentTargetID,
|
|
Provider: "codex",
|
|
Cwd: cwd,
|
|
Status: "active",
|
|
OccurredAtUnixMS: 10,
|
|
}); err != nil {
|
|
t.Fatalf("ReportSessionState(%s) error = %v", sessionID, err)
|
|
}
|
|
}
|
|
|
|
func recordGeneratedFileTurn(
|
|
t *testing.T,
|
|
ctx context.Context,
|
|
store *Store,
|
|
workspaceID string,
|
|
sessionID string,
|
|
turnID string,
|
|
occurredAt int64,
|
|
files []any,
|
|
) {
|
|
t.Helper()
|
|
if _, accepted, err := store.RecordTurnTransition(ctx, TurnTransition{
|
|
WorkspaceID: workspaceID,
|
|
AgentSessionID: sessionID,
|
|
TurnID: turnID,
|
|
Phase: TurnPhaseSettled,
|
|
Outcome: TurnOutcomeCompleted,
|
|
Origin: TurnOriginLegacyUnknown,
|
|
FileChanges: map[string]any{"files": files},
|
|
OccurredAtUnixMS: occurredAt,
|
|
}); err != nil || !accepted {
|
|
t.Fatalf("RecordTurnTransition(%s) accepted=%v error=%v", turnID, accepted, err)
|
|
}
|
|
}
|