Reconcile removed analysis artifacts
This commit is contained in:
@@ -71,25 +71,19 @@ func (analyzeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("analyze: resolve run-stage layout: %w", err)
|
||||
}
|
||||
if env.Config.Pipeline.Scriptorium == nil {
|
||||
return &StageResult{Metadata: map[string]any{
|
||||
"stage": "analyze",
|
||||
"skipped": true,
|
||||
"reason": "pipeline.scriptorium is not configured",
|
||||
}}, nil
|
||||
}
|
||||
if len(env.Config.Pipeline.Scriptorium.Artifacts) == 0 {
|
||||
return &StageResult{Metadata: map[string]any{
|
||||
"stage": "analyze",
|
||||
"skipped": true,
|
||||
"reason": "no scriptorium artifacts configured",
|
||||
}}, nil
|
||||
scriptoriumCfg := env.Config.Pipeline.Scriptorium
|
||||
noWorkReason := ""
|
||||
if scriptoriumCfg == nil {
|
||||
noWorkReason = "pipeline.scriptorium is not configured"
|
||||
scriptoriumCfg = &config.ScriptoriumConfig{}
|
||||
} else if len(scriptoriumCfg.Artifacts) == 0 {
|
||||
noWorkReason = "no scriptorium artifacts configured"
|
||||
}
|
||||
|
||||
effective := env.EffectiveArtifacts
|
||||
if !effective.Resolved() {
|
||||
effective, err = artifacts.ResolveEffectiveArtifactSet(
|
||||
artifacts.ConfiguredArtifactDefinitions(env.Config.Pipeline.Scriptorium.Artifacts),
|
||||
artifacts.ConfiguredArtifactDefinitions(scriptoriumCfg.Artifacts),
|
||||
env.SelectedArtifactKeys,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -100,7 +94,7 @@ func (analyzeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S
|
||||
runtimeCatalog, err := buildAnalyzeRuntimeArtifactCatalog(
|
||||
paths,
|
||||
m,
|
||||
env.Config.Pipeline.Scriptorium,
|
||||
scriptoriumCfg,
|
||||
env.Config.Pipeline.Notarius,
|
||||
effective,
|
||||
)
|
||||
@@ -108,12 +102,8 @@ func (analyzeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S
|
||||
return nil, fmt.Errorf("analyze: build runtime artifact catalog: %w", err)
|
||||
}
|
||||
|
||||
if len(effective.Keys()) == 0 {
|
||||
return &StageResult{Metadata: map[string]any{
|
||||
"stage": "analyze",
|
||||
"skipped": true,
|
||||
"reason": "no selected scriptorium artifacts to execute",
|
||||
}}, nil
|
||||
if len(effective.Keys()) == 0 && noWorkReason == "" {
|
||||
noWorkReason = "no selected scriptorium artifacts to execute"
|
||||
}
|
||||
|
||||
execution := analyzeExecutionContext{
|
||||
@@ -125,12 +115,12 @@ func (analyzeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S
|
||||
TranscriptRefs: discoverAnalyzeTranscriptRefs(m, paths),
|
||||
Catalog: runtimeCatalog,
|
||||
}
|
||||
reconciliation, err := reconcileAnalyzeArtifacts(env.Config.Pipeline.Scriptorium, execution)
|
||||
reconciliation, err := reconcileAnalyzeArtifacts(scriptoriumCfg, execution)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("analyze: reconcile configured artifacts: %w", err)
|
||||
}
|
||||
workPlan, err := planAnalyzeWork(
|
||||
env.Config.Pipeline.Scriptorium,
|
||||
scriptoriumCfg,
|
||||
env.SelectedArtifactKeys,
|
||||
env.Force,
|
||||
reconciliation,
|
||||
@@ -148,10 +138,10 @@ func (analyzeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S
|
||||
}
|
||||
|
||||
for _, item := range workPlan.ExecutionOrder {
|
||||
artifactCfg := env.Config.Pipeline.Scriptorium.Artifacts[item.Key]
|
||||
artifactCfg := scriptoriumCfg.Artifacts[item.Key]
|
||||
fingerprint, _, _, err := computeAnalyzeArtifactFingerprint(
|
||||
item.Key,
|
||||
env.Config.Pipeline.Scriptorium,
|
||||
scriptoriumCfg,
|
||||
execution,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -167,7 +157,12 @@ func (analyzeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S
|
||||
return state.failedResult(item, artifactCfg, fingerprint, err), err
|
||||
}
|
||||
}
|
||||
return state.successResult(), nil
|
||||
result := state.successResult()
|
||||
if noWorkReason != "" {
|
||||
result.Metadata["skipped"] = true
|
||||
result.Metadata["reason"] = noWorkReason
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func analyzeArtifactOrigin(execution analyzeExecutionContext, key string) (config.ArtifactFamilyMemberOrigin, bool) {
|
||||
|
||||
@@ -15,8 +15,8 @@ func (analyzeStage) ValidateResume(_ context.Context, env *Env, m *manifest.Mani
|
||||
return ResumeValidation{}, fmt.Errorf("analyze resume: resolved stage environment config is required")
|
||||
}
|
||||
cfg := env.Config.Pipeline.Scriptorium
|
||||
if cfg == nil || len(cfg.Artifacts) == 0 {
|
||||
return ResumeValidation{Resumable: true, Analyze: &AnalyzeResumeSummary{}}, nil
|
||||
if cfg == nil {
|
||||
cfg = &config.ScriptoriumConfig{}
|
||||
}
|
||||
sessionID := ""
|
||||
if m != nil {
|
||||
@@ -39,9 +39,6 @@ func (analyzeStage) ValidateResume(_ context.Context, env *Env, m *manifest.Mani
|
||||
return ResumeValidation{}, fmt.Errorf("analyze resume: resolve effective artifacts: %w", err)
|
||||
}
|
||||
}
|
||||
if len(effective.Keys()) == 0 {
|
||||
return ResumeValidation{Resumable: true, Analyze: &AnalyzeResumeSummary{}}, nil
|
||||
}
|
||||
paths := sessionPathsForEnv(env, sessionID)
|
||||
catalog, err := buildAnalyzeRuntimeArtifactCatalog(
|
||||
paths, m, cfg, env.Config.Pipeline.Notarius, effective,
|
||||
@@ -62,9 +59,16 @@ func (analyzeStage) ValidateResume(_ context.Context, env *Env, m *manifest.Mani
|
||||
return ResumeValidation{}, fmt.Errorf("analyze resume: plan configured artifacts: %w", err)
|
||||
}
|
||||
summary := analyzeResumeSummary(plan, env.Config.Pipeline)
|
||||
if len(plan.ExecutionOrder) == 0 {
|
||||
if len(plan.ExecutionOrder) == 0 && len(plan.Removed) == 0 {
|
||||
return ResumeValidation{Resumable: true, Analyze: summary}, nil
|
||||
}
|
||||
if len(plan.ExecutionOrder) == 0 {
|
||||
keys := analyzePlanKeysForMetadata(plan.Removed)
|
||||
return ResumeValidation{
|
||||
Reason: "analysis artifact state requires reconciliation: " + strings.Join(keys, ", "),
|
||||
Analyze: summary,
|
||||
}, nil
|
||||
}
|
||||
keys := analyzePlanKeysForMetadata(plan.ExecutionOrder)
|
||||
return ResumeValidation{
|
||||
Reason: "analysis artifacts require execution: " + strings.Join(keys, ", "),
|
||||
|
||||
@@ -4,8 +4,11 @@ import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/adapters/scriptorium"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/config"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
|
||||
)
|
||||
@@ -37,6 +40,69 @@ func TestAnalyzeResumeValidationHonorsFullAndPartialSelections(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeRemovedConfigurationRequiresAndPersistsReconciliation(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
remove func(*config.PipelineConfig)
|
||||
reason string
|
||||
}{
|
||||
{
|
||||
name: "scriptorium omitted",
|
||||
remove: func(pipeline *config.PipelineConfig) {
|
||||
pipeline.Scriptorium = nil
|
||||
},
|
||||
reason: "pipeline.scriptorium is not configured",
|
||||
},
|
||||
{
|
||||
name: "artifact map empty",
|
||||
remove: func(pipeline *config.PipelineConfig) {
|
||||
pipeline.Scriptorium.Artifacts = map[string]config.ScriptoriumArtifactConfig{}
|
||||
},
|
||||
reason: "no scriptorium artifacts configured",
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
env, m, initialRequests := currentAnalyzeFixture(t, false)
|
||||
test.remove(env.Config.Pipeline)
|
||||
|
||||
validation, err := (analyzeStage{}).ValidateResume(context.Background(), env, m)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if validation.Resumable || !strings.Contains(validation.Reason, "session_recap") {
|
||||
t.Fatalf("validation = %#v, want reconciliation of removed session_recap", validation)
|
||||
}
|
||||
|
||||
result, err := (analyzeStage{}).Run(context.Background(), env, m)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.Metadata["skipped"] != true || result.Metadata["reason"] != test.reason {
|
||||
t.Fatalf("metadata = %#v, want skipped reason %q", result.Metadata, test.reason)
|
||||
}
|
||||
if len(result.Outputs) != 0 || len(m.Stages["analyze"].AnalyzeArtifacts) != 0 {
|
||||
t.Fatalf("result outputs = %#v; analyze records = %#v, want both empty", result.Outputs, m.Stages["analyze"].AnalyzeArtifacts)
|
||||
}
|
||||
fake := env.Scriptorium.(*scriptorium.FakeRunner)
|
||||
if len(fake.RunRequests) != initialRequests {
|
||||
t.Fatalf("adapter requests = %d, want unchanged count %d", len(fake.RunRequests), initialRequests)
|
||||
}
|
||||
|
||||
paths := sessionPathsForEnv(env, m.SessionID)
|
||||
stored, err := env.ManifestStore.Load(
|
||||
context.Background(),
|
||||
artifacts.SessionManifestPathForCampaign(paths.WorkspaceRoot, m.Campaign, m.SessionID),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(stored.Stages["analyze"].AnalyzeArtifacts) != 0 {
|
||||
t.Fatalf("durable analyze records = %#v, want empty", stored.Stages["analyze"].AnalyzeArtifacts)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeResumeValidationReportsForceAndCurrentPrerequisites(t *testing.T) {
|
||||
env, m, _ := currentAnalyzeFixture(t, true)
|
||||
env.SelectedArtifactKeys = []string{"session_recap"}
|
||||
|
||||
Reference in New Issue
Block a user