Record effective sessions in debug provenance

This commit is contained in:
2026-08-03 19:09:49 +00:00
parent 7a4fd7be7a
commit d9dae2b639
6 changed files with 102 additions and 1 deletions

View File

@@ -249,10 +249,24 @@ func TestRunAutoReusesPlanWhenRunInputsChange(t *testing.T) {
}
harness.mu.Lock()
chunkCalls := harness.chunkCalls
sessions := append([]string(nil), harness.sessionIDs...)
harness.mu.Unlock()
if chunkCalls != 1 {
t.Fatalf("chunk calls across changed run inputs = %d, want 1", chunkCalls)
}
rawInput, err := os.ReadFile(roots.input)
if err != nil {
t.Fatal(err)
}
wantSessionID, err := resolvePromptSessionID("", "test/input", rawInput)
if err != nil {
t.Fatal(err)
}
for _, sessionID := range sessions {
if sessionID != wantSessionID {
t.Fatalf("session IDs across reference changes = %#v, want %q", sessions, wantSessionID)
}
}
assertFile(t, filepath.Join(roots.plans, strings.TrimPrefix(stateTestDigest, "sha256:"), "plan.json"))
assertAnyFile(t, roots.output)
}

View File

@@ -418,6 +418,10 @@ func runPipelineCommand(args []string, stdout, stderr io.Writer, opts Options) i
if err != nil {
return failPipelineCommand(stderr, commandState, terminalWriter, err)
}
invocation.SessionID = effectiveSessionID
if err := writeSummary(summary, func() error { return summary.WriteInvocation(invocation) }); err != nil {
return failPipelineCommand(stderr, commandState, terminalWriter, fmt.Errorf("write debug invocation metadata: %w", err))
}
chunkPlans, err := chunkPlanStoreForRun(effective.Config.Cache.ChunkPlans, opts)
if err != nil {
return failPipelineCommand(stderr, commandState, terminalWriter, err)

View File

@@ -515,7 +515,7 @@ func TestRunSessionIDUsesEffectiveValueForPromptRequests(t *testing.T) {
}
}
harness := newStateTestHarness()
args := append([]string{"run", "sample", "--config", roots.config, "--input", roots.input, "--chunk_cache", "bypass"}, tt.args...)
args := append([]string{"run", "sample", "--config", roots.config, "--input", roots.input, "--chunk_cache", "bypass", "--debug"}, tt.args...)
var stdout, stderr bytes.Buffer
code := RunWithOptions(args, &stdout, &stderr, harness.options())
if code != 0 || stderr.Len() != 0 {
@@ -532,6 +532,11 @@ func TestRunSessionIDUsesEffectiveValueForPromptRequests(t *testing.T) {
t.Fatalf("session IDs = %#v, want %q", sessions, want)
}
}
var manifest artifacts.RunManifest
readStateTestSummaryJSON(t, onlyChildDir(t, roots.debug), "run-manifest.json", &manifest)
if session, ok := manifest.Metadata["session_id"]; !ok || session != want {
t.Fatalf("manifest session = %#v, want %q; metadata = %#v", session, want, manifest.Metadata)
}
})
}
}

View File

@@ -329,11 +329,25 @@ func TestRunUsesOneInjectedIdentityForDebugOutputAndManifest(t *testing.T) {
if manifest.StartedAt == nil || !manifest.StartedAt.Equal(wantStartedAt) {
t.Fatalf("manifest started at = %v, want %v", manifest.StartedAt, wantStartedAt)
}
rawInput, err := os.ReadFile(roots.input)
if err != nil {
t.Fatal(err)
}
wantSessionID, err := resolvePromptSessionID("", "test/input", rawInput)
if err != nil {
t.Fatal(err)
}
if sessionID, ok := manifest.Metadata["session_id"]; !ok || sessionID != wantSessionID {
t.Fatalf("manifest session = %#v, want %q; metadata = %#v", sessionID, wantSessionID, manifest.Metadata)
}
var invocation debugbundle.Invocation
readStateTestSummaryJSON(t, debugPath, "invocation.json", &invocation)
if invocation.RunID != runID || !invocation.StartedAt.Equal(wantStartedAt) {
t.Fatalf("debug invocation identity = %#v, want run %q at %v", invocation, runID, wantStartedAt)
}
if invocation.SessionID != wantSessionID {
t.Fatalf("debug invocation session = %q, want %q", invocation.SessionID, wantSessionID)
}
report := readStateTestRunReport(t, debugPath)
if !report.Succeeded || report.RunID != runID || report.PipelineID != "sample" || report.OutputPath != outputPath || report.DebugPath != debugPath || report.OutputCount != 1 || report.RejectedCount != 0 || report.WarningCount != 0 || report.ValidationStatus != "approved" {
t.Fatalf("success report = %#v", report)
@@ -343,6 +357,45 @@ func TestRunUsesOneInjectedIdentityForDebugOutputAndManifest(t *testing.T) {
}
}
func TestRunCheckpointReuseRequiresSameEffectiveSession(t *testing.T) {
roots := newStateTestRoots(t)
harness := newStateTestHarness()
run := func(extra ...string) stateTestResult {
args := []string{"run", "sample", "--config", roots.config, "--input", roots.input, "--chunk_cache", "bypass"}
args = append(args, extra...)
var stdout, stderr bytes.Buffer
return stateTestResult{code: RunWithOptions(args, &stdout, &stderr, harness.options()), stdout: stdout.String(), stderr: stderr.String()}
}
if result := run(); result.code != 0 {
t.Fatalf("initial run code=%d stdout=%q stderr=%q", result.code, result.stdout, result.stderr)
}
harness.mu.Lock()
initialExtractCalls := harness.extractCalls
harness.mu.Unlock()
if initialExtractCalls != 1 {
t.Fatalf("initial extract calls = %d, want 1", initialExtractCalls)
}
if result := run("--resume"); result.code != 0 {
t.Fatalf("same-session resume code=%d stdout=%q stderr=%q", result.code, result.stdout, result.stderr)
}
harness.mu.Lock()
reusedExtractCalls := harness.extractCalls
harness.mu.Unlock()
if reusedExtractCalls != initialExtractCalls {
t.Fatalf("extract calls after same-session resume = %d, want %d", reusedExtractCalls, initialExtractCalls)
}
if result := run("--resume", "--session-id", "different-session"); result.code != 0 {
t.Fatalf("different-session resume code=%d stdout=%q stderr=%q", result.code, result.stdout, result.stderr)
}
harness.mu.Lock()
differentSessionExtractCalls := harness.extractCalls
harness.mu.Unlock()
if differentSessionExtractCalls != initialExtractCalls+1 {
t.Fatalf("extract calls after different-session resume = %d, want %d", differentSessionExtractCalls, initialExtractCalls+1)
}
}
func TestRunWritesTerminalArtifactsForResolutionPipelineAndOutputFailures(t *testing.T) {
for _, tc := range []struct {
name string

View File

@@ -179,6 +179,30 @@ func TestWriteInvocationPreservesReasoningEffortOverrideStates(t *testing.T) {
})
}
}
func TestWriteInvocationOmitsEmptySessionID(t *testing.T) {
for _, sessionID := range []string{"", "session-123"} {
bundle, err := Allocate(t.TempDir(), testBundleRunID, time.Unix(0, 42))
if err != nil {
t.Fatal(err)
}
if err := bundle.Summary().WriteInvocation(Invocation{Operation: "run", SessionID: sessionID}); err != nil {
t.Fatal(err)
}
data, err := os.ReadFile(filepath.Join(bundle.SummaryRoot(), ArtifactInvocationMetadata))
if err != nil {
t.Fatal(err)
}
var payload map[string]any
if err := json.Unmarshal(data, &payload); err != nil {
t.Fatal(err)
}
value, found := payload["session_id"]
if found != (sessionID != "") || (found && value != sessionID) {
t.Fatalf("session found=%t value=%#v, want found=%t value=%q; JSON=%s", found, value, sessionID != "", sessionID, data)
}
}
}
func TestSummaryWriterInternalWritesConfineArtifacts(t *testing.T) {
bundle, err := Allocate(t.TempDir(), testBundleRunID, time.Unix(0, 42))
if err != nil {

View File

@@ -40,6 +40,7 @@ type Invocation struct {
OnlyLanes []string `json:"only_lanes,omitempty"`
ChunkCacheOverride string `json:"chunk_cache_override,omitempty"`
ReasoningEffortOverride *string `json:"reasoning_effort_override,omitempty"`
SessionID string `json:"session_id,omitempty"`
RunID string `json:"run_id"`
StartedAt time.Time `json:"started_at"`
}