Record effective sessions in debug provenance
This commit is contained in:
@@ -249,10 +249,24 @@ func TestRunAutoReusesPlanWhenRunInputsChange(t *testing.T) {
|
|||||||
}
|
}
|
||||||
harness.mu.Lock()
|
harness.mu.Lock()
|
||||||
chunkCalls := harness.chunkCalls
|
chunkCalls := harness.chunkCalls
|
||||||
|
sessions := append([]string(nil), harness.sessionIDs...)
|
||||||
harness.mu.Unlock()
|
harness.mu.Unlock()
|
||||||
if chunkCalls != 1 {
|
if chunkCalls != 1 {
|
||||||
t.Fatalf("chunk calls across changed run inputs = %d, want 1", chunkCalls)
|
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"))
|
assertFile(t, filepath.Join(roots.plans, strings.TrimPrefix(stateTestDigest, "sha256:"), "plan.json"))
|
||||||
assertAnyFile(t, roots.output)
|
assertAnyFile(t, roots.output)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -418,6 +418,10 @@ func runPipelineCommand(args []string, stdout, stderr io.Writer, opts Options) i
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return failPipelineCommand(stderr, commandState, terminalWriter, err)
|
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)
|
chunkPlans, err := chunkPlanStoreForRun(effective.Config.Cache.ChunkPlans, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return failPipelineCommand(stderr, commandState, terminalWriter, err)
|
return failPipelineCommand(stderr, commandState, terminalWriter, err)
|
||||||
|
|||||||
@@ -515,7 +515,7 @@ func TestRunSessionIDUsesEffectiveValueForPromptRequests(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
harness := newStateTestHarness()
|
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
|
var stdout, stderr bytes.Buffer
|
||||||
code := RunWithOptions(args, &stdout, &stderr, harness.options())
|
code := RunWithOptions(args, &stdout, &stderr, harness.options())
|
||||||
if code != 0 || stderr.Len() != 0 {
|
if code != 0 || stderr.Len() != 0 {
|
||||||
@@ -532,6 +532,11 @@ func TestRunSessionIDUsesEffectiveValueForPromptRequests(t *testing.T) {
|
|||||||
t.Fatalf("session IDs = %#v, want %q", sessions, want)
|
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)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -329,11 +329,25 @@ func TestRunUsesOneInjectedIdentityForDebugOutputAndManifest(t *testing.T) {
|
|||||||
if manifest.StartedAt == nil || !manifest.StartedAt.Equal(wantStartedAt) {
|
if manifest.StartedAt == nil || !manifest.StartedAt.Equal(wantStartedAt) {
|
||||||
t.Fatalf("manifest started at = %v, want %v", manifest.StartedAt, 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
|
var invocation debugbundle.Invocation
|
||||||
readStateTestSummaryJSON(t, debugPath, "invocation.json", &invocation)
|
readStateTestSummaryJSON(t, debugPath, "invocation.json", &invocation)
|
||||||
if invocation.RunID != runID || !invocation.StartedAt.Equal(wantStartedAt) {
|
if invocation.RunID != runID || !invocation.StartedAt.Equal(wantStartedAt) {
|
||||||
t.Fatalf("debug invocation identity = %#v, want run %q at %v", invocation, runID, 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)
|
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" {
|
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)
|
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) {
|
func TestRunWritesTerminalArtifactsForResolutionPipelineAndOutputFailures(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@@ -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) {
|
func TestSummaryWriterInternalWritesConfineArtifacts(t *testing.T) {
|
||||||
bundle, err := Allocate(t.TempDir(), testBundleRunID, time.Unix(0, 42))
|
bundle, err := Allocate(t.TempDir(), testBundleRunID, time.Unix(0, 42))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ type Invocation struct {
|
|||||||
OnlyLanes []string `json:"only_lanes,omitempty"`
|
OnlyLanes []string `json:"only_lanes,omitempty"`
|
||||||
ChunkCacheOverride string `json:"chunk_cache_override,omitempty"`
|
ChunkCacheOverride string `json:"chunk_cache_override,omitempty"`
|
||||||
ReasoningEffortOverride *string `json:"reasoning_effort_override,omitempty"`
|
ReasoningEffortOverride *string `json:"reasoning_effort_override,omitempty"`
|
||||||
|
SessionID string `json:"session_id,omitempty"`
|
||||||
RunID string `json:"run_id"`
|
RunID string `json:"run_id"`
|
||||||
StartedAt time.Time `json:"started_at"`
|
StartedAt time.Time `json:"started_at"`
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user