Clear superseded session stage result details

This commit is contained in:
2026-08-10 02:53:28 +00:00
parent b99bd38eb4
commit b556fc2f4f
6 changed files with 127 additions and 21 deletions

View File

@@ -88,6 +88,7 @@ func New(sessionID string, now time.Time) *Manifest {
// MarkStageRunning marks a stage as running and updates timestamps.
func (m *Manifest) MarkStageRunning(name string, at time.Time) {
s := m.ensureStage(name, at)
s.clearResultDetails()
s.Status = StatusRunning
s.StartedAt = timePtr(at)
s.CompletedAt = nil
@@ -110,6 +111,7 @@ func (m *Manifest) MarkStageSucceeded(name string, at time.Time, outputs []Artif
// MarkStageFailed marks a stage as failed and records error metadata.
func (m *Manifest) MarkStageFailed(name string, at time.Time, message string) {
s := m.ensureStage(name, at)
s.clearResultDetails()
s.Status = StatusFailed
s.CompletedAt = timePtr(at)
s.Error = &ErrorRecord{Message: strings.TrimSpace(message), At: timePtr(at)}
@@ -121,10 +123,10 @@ func (m *Manifest) MarkStageFailed(name string, at time.Time, message string) {
// MarkStageSkipped marks a stage as skipped and records the skip reason.
func (m *Manifest) MarkStageSkipped(name string, at time.Time, reason string) {
s := m.ensureStage(name, at)
s.clearResultDetails()
s.Status = StatusSkipped
s.CompletedAt = timePtr(at)
s.Error = &ErrorRecord{Message: strings.TrimSpace(reason), Code: "skipped", At: timePtr(at)}
s.Outputs = nil
s.UpdatedAt = at
m.UpdatedAt = at
}
@@ -138,6 +140,13 @@ func (m *Manifest) MarkStageStale(name string, at time.Time, reason string) {
m.UpdatedAt = at
}
func (s *StageRecord) clearResultDetails() {
s.Outputs = nil
s.Logs = nil
s.GeneratedConfigs = nil
s.Metadata = nil
}
func (m *Manifest) ensureStage(name string, at time.Time) *StageRecord {
if m.Stages == nil {
m.Stages = map[string]*StageRecord{}

View File

@@ -69,11 +69,12 @@ func TestStageMarkHelpers(t *testing.T) {
}
}
func TestMarkStageRunningClearsPriorCompletionAndError(t *testing.T) {
func TestMarkStageRunningClearsPriorCompletionErrorAndResultDetails(t *testing.T) {
m := New("2026-05-03", time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC))
failedAt := time.Date(2026, 5, 3, 10, 1, 0, 0, time.UTC)
m.MarkStageFailed("merge", failedAt, "boom")
setStageResultDetails(m.Stages["merge"])
runAt := failedAt.Add(1 * time.Minute)
m.MarkStageRunning("merge", runAt)
@@ -91,6 +92,7 @@ func TestMarkStageRunningClearsPriorCompletionAndError(t *testing.T) {
if stage.Error != nil {
t.Fatalf("error = %#v, want nil while running", stage.Error)
}
requireStageResultDetailsCleared(t, stage)
}
func TestMarkStageSucceededClearsError(t *testing.T) {
@@ -111,11 +113,24 @@ func TestMarkStageSucceededClearsError(t *testing.T) {
}
}
func TestMarkStageSkippedClearsEarlierOutputs(t *testing.T) {
func TestMarkStageFailedClearsEarlierResultDetails(t *testing.T) {
m := New("2026-05-03", time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC))
m.MarkStageSucceeded("extract", time.Date(2026, 5, 3, 10, 1, 0, 0, time.UTC), []ArtifactRecord{
{Kind: "structured_data", SourceID: "narratio.example.characters", LocalPath: "artifacts/characters.json"},
})
m.MarkStageSucceeded("extract", time.Date(2026, 5, 3, 10, 1, 0, 0, time.UTC), nil)
setStageResultDetails(m.Stages["extract"])
m.MarkStageFailed("extract", time.Date(2026, 5, 3, 10, 2, 0, 0, time.UTC), "replacement failed")
stage := m.Stages["extract"]
if stage == nil || stage.Status != StatusFailed {
t.Fatalf("stage = %#v, want failed", stage)
}
requireStageResultDetailsCleared(t, stage)
}
func TestMarkStageSkippedClearsEarlierResultDetails(t *testing.T) {
m := New("2026-05-03", time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC))
m.MarkStageSucceeded("extract", time.Date(2026, 5, 3, 10, 1, 0, 0, time.UTC), nil)
setStageResultDetails(m.Stages["extract"])
m.MarkStageSkipped("extract", time.Date(2026, 5, 3, 10, 2, 0, 0, time.UTC), "integration_disabled")
@@ -126,7 +141,39 @@ func TestMarkStageSkippedClearsEarlierOutputs(t *testing.T) {
if stage.Status != StatusSkipped {
t.Fatalf("status = %q, want %q", stage.Status, StatusSkipped)
}
if len(stage.Outputs) != 0 {
t.Fatalf("outputs = %#v, want cleared", stage.Outputs)
requireStageResultDetailsCleared(t, stage)
}
func TestMarkStageStalePreservesResultDetails(t *testing.T) {
m := New("2026-05-03", time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC))
m.MarkStageSucceeded("extract", time.Date(2026, 5, 3, 10, 1, 0, 0, time.UTC), nil)
stage := m.Stages["extract"]
setStageResultDetails(stage)
m.MarkStageStale("extract", time.Date(2026, 5, 3, 10, 2, 0, 0, time.UTC), "result is not resumable")
if stage.Status != StatusStale {
t.Fatalf("status = %q, want %q", stage.Status, StatusStale)
}
if len(stage.Outputs) != 1 || len(stage.Logs) != 1 || len(stage.GeneratedConfigs) != 1 || len(stage.Metadata) != 1 {
t.Fatalf("result details were not preserved: %#v", stage)
}
}
func setStageResultDetails(stage *StageRecord) {
stage.Outputs = []ArtifactRecord{{
Kind: "structured_data",
SourceID: "narratio.example.characters",
LocalPath: "artifacts/characters.json",
}}
stage.Logs = []string{"logs/extract.log"}
stage.GeneratedConfigs = []string{"generated/extract.yaml"}
stage.Metadata = map[string]any{"bundle_path": "extract/results/run-1"}
}
func requireStageResultDetailsCleared(t *testing.T, stage *StageRecord) {
t.Helper()
if len(stage.Outputs) != 0 || len(stage.Logs) != 0 || len(stage.GeneratedConfigs) != 0 || len(stage.Metadata) != 0 {
t.Fatalf("result details were not cleared: %#v", stage)
}
}