Preserve incremental analysis state on failure
This commit is contained in:
@@ -177,13 +177,30 @@ func (analyzeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S
|
||||
execution,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("analyze: compute execution fingerprint for artifact %q: %w", item.Key, err)
|
||||
executionErr := fmt.Errorf("analyze: compute execution fingerprint for artifact %q: %w", item.Key, err)
|
||||
return failedAnalyzeResult(
|
||||
execution,
|
||||
item,
|
||||
artifactCfg,
|
||||
"",
|
||||
executionErr,
|
||||
sessionRecords,
|
||||
invocationRecords,
|
||||
), executionErr
|
||||
}
|
||||
priorRecord, hadPriorRecord := priorCurrentRecords[item.Key]
|
||||
plan := analyzeArtifactExecutionPlan{Name: item.Key, Cfg: artifactCfg}
|
||||
artifactResult, err := executeAnalyzeArtifact(ctx, execution, plan)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return failedAnalyzeResult(
|
||||
execution,
|
||||
item,
|
||||
artifactCfg,
|
||||
fingerprint,
|
||||
err,
|
||||
sessionRecords,
|
||||
invocationRecords,
|
||||
), err
|
||||
}
|
||||
|
||||
logs = append(logs, artifactResult.Logs...)
|
||||
@@ -207,14 +224,27 @@ func (analyzeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S
|
||||
artifactResult,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("analyze: record artifact %q: %w", plan.Name, err)
|
||||
recordErr := fmt.Errorf("analyze: record artifact %q: %w", plan.Name, err)
|
||||
return failedAnalyzeResult(
|
||||
execution,
|
||||
item,
|
||||
artifactCfg,
|
||||
fingerprint,
|
||||
recordErr,
|
||||
sessionRecords,
|
||||
invocationRecords,
|
||||
), recordErr
|
||||
}
|
||||
sessionRecords[plan.Name] = record
|
||||
invocationRecords[plan.Name] = record
|
||||
|
||||
sourceID, ok := runtimeCatalog.SourceIDForConfiguredKey(plan.Name)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("analyze: source id not found for artifact %q", plan.Name)
|
||||
catalogErr := fmt.Errorf("analyze: source id not found for artifact %q", plan.Name)
|
||||
return failedAnalyzeResult(
|
||||
execution, item, artifactCfg, fingerprint, catalogErr,
|
||||
sessionRecords, invocationRecords,
|
||||
), catalogErr
|
||||
}
|
||||
if err := runtimeCatalog.MarkAvailableGeneratedEvidence(
|
||||
sourceID,
|
||||
@@ -224,7 +254,11 @@ func (analyzeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S
|
||||
record.OutputSize,
|
||||
record.Output.Contract,
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("analyze: mark generated artifact %q available: %w", sourceID, err)
|
||||
catalogErr := fmt.Errorf("analyze: mark generated artifact %q available: %w", sourceID, err)
|
||||
return failedAnalyzeResult(
|
||||
execution, item, artifactCfg, fingerprint, catalogErr,
|
||||
sessionRecords, invocationRecords,
|
||||
), catalogErr
|
||||
}
|
||||
if !hadPriorRecord || !sameAnalyzeOutputIdentity(priorRecord, record) {
|
||||
staleUnscheduledAnalyzeDependents(
|
||||
@@ -263,6 +297,77 @@ func (analyzeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S
|
||||
}, nil
|
||||
}
|
||||
|
||||
func failedAnalyzeResult(
|
||||
execution analyzeExecutionContext,
|
||||
item analyzePlanItem,
|
||||
artifactCfg config.ScriptoriumArtifactConfig,
|
||||
fingerprint string,
|
||||
cause error,
|
||||
sessionRecords map[string]manifest.AnalyzeArtifactRecord,
|
||||
invocationRecords map[string]manifest.AnalyzeArtifactRecord,
|
||||
) *StageResult {
|
||||
record := manifest.AnalyzeArtifactRecord{
|
||||
Key: item.Key,
|
||||
Status: manifest.AnalyzeArtifactFailed,
|
||||
Dependencies: normalizedAnalyzeDependencyKeys(artifactCfg.DependsOn),
|
||||
ProducerRunID: analyzeProducerRunID(execution),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
Error: NonResumable(cause.Error()).Reason,
|
||||
}
|
||||
if fingerprint != "" {
|
||||
record.FingerprintVersion = manifest.AnalyzeFingerprintContractVersion
|
||||
record.Fingerprint = fingerprint
|
||||
}
|
||||
if artifactCfg.PromptID != "" || artifactCfg.ProfileID != "" {
|
||||
record.Scriptorium = &manifest.AnalyzeArtifactProvenance{
|
||||
PromptID: artifactCfg.PromptID, ProfileID: artifactCfg.ProfileID,
|
||||
}
|
||||
}
|
||||
sessionRecords[item.Key] = record
|
||||
invocationRecords[item.Key] = record
|
||||
staleAnalyzeDependents(artifactCfgMap(execution), item.Key, sessionRecords)
|
||||
return &StageResult{AnalyzeState: &AnalyzeStateProjection{
|
||||
Session: manifest.CloneAnalyzeArtifactCollection(sessionRecords),
|
||||
Invocation: manifest.CloneAnalyzeArtifactCollection(invocationRecords),
|
||||
}}
|
||||
}
|
||||
|
||||
func artifactCfgMap(execution analyzeExecutionContext) map[string]config.ScriptoriumArtifactConfig {
|
||||
if execution.Env == nil || execution.Env.Config == nil || execution.Env.Config.Pipeline == nil ||
|
||||
execution.Env.Config.Pipeline.Scriptorium == nil {
|
||||
return nil
|
||||
}
|
||||
return execution.Env.Config.Pipeline.Scriptorium.Artifacts
|
||||
}
|
||||
|
||||
func staleAnalyzeDependents(
|
||||
configured map[string]config.ScriptoriumArtifactConfig,
|
||||
changed string,
|
||||
records map[string]manifest.AnalyzeArtifactRecord,
|
||||
) {
|
||||
reverse := make(map[string][]string, len(configured))
|
||||
for key, artifactCfg := range configured {
|
||||
for _, dependency := range normalizedAnalyzeDependencyKeys(artifactCfg.DependsOn) {
|
||||
reverse[dependency] = append(reverse[dependency], key)
|
||||
}
|
||||
}
|
||||
for key := range reverse {
|
||||
sort.Strings(reverse[key])
|
||||
}
|
||||
queue := append([]string(nil), reverse[changed]...)
|
||||
seen := make(map[string]struct{}, len(queue))
|
||||
for len(queue) > 0 {
|
||||
key := queue[0]
|
||||
queue = queue[1:]
|
||||
if _, visited := seen[key]; visited {
|
||||
continue
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
staleProjectedAnalyzeRecord(records, key)
|
||||
queue = append(queue, reverse[key]...)
|
||||
}
|
||||
}
|
||||
|
||||
func currentAnalyzeArtifactRecord(
|
||||
execution analyzeExecutionContext,
|
||||
plan analyzeArtifactExecutionPlan,
|
||||
@@ -272,15 +377,7 @@ func currentAnalyzeArtifactRecord(
|
||||
if result == nil {
|
||||
return manifest.AnalyzeArtifactRecord{}, fmt.Errorf("execution result is required")
|
||||
}
|
||||
producerRunID := ""
|
||||
if execution.Manifest != nil {
|
||||
producerRunID = strings.TrimSpace(execution.Manifest.RunID)
|
||||
}
|
||||
if producerRunID == "" {
|
||||
// Direct stage callers predate invocation manifests. Application-owned
|
||||
// execution always supplies the actual run identity.
|
||||
producerRunID = "direct-analyze"
|
||||
}
|
||||
producerRunID := analyzeProducerRunID(execution)
|
||||
relativePath, err := normalizedAnalyzeOutputIdentity(plan.Cfg.OutputPath)
|
||||
if err != nil {
|
||||
return manifest.AnalyzeArtifactRecord{}, err
|
||||
@@ -322,6 +419,17 @@ func currentAnalyzeArtifactRecord(
|
||||
return record, nil
|
||||
}
|
||||
|
||||
func analyzeProducerRunID(execution analyzeExecutionContext) string {
|
||||
if execution.Manifest != nil {
|
||||
if runID := strings.TrimSpace(execution.Manifest.RunID); runID != "" {
|
||||
return runID
|
||||
}
|
||||
}
|
||||
// Direct stage callers predate invocation manifests. Application-owned
|
||||
// execution always supplies the actual run identity.
|
||||
return "direct-analyze"
|
||||
}
|
||||
|
||||
func sameAnalyzeOutputIdentity(left, right manifest.AnalyzeArtifactRecord) bool {
|
||||
if left.Status != manifest.AnalyzeArtifactCurrent || right.Status != manifest.AnalyzeArtifactCurrent ||
|
||||
left.Output == nil || right.Output == nil || left.Output.Contract == nil || right.Output.Contract == nil {
|
||||
|
||||
Reference in New Issue
Block a user