Unify pre-execution stage decisions

This commit is contained in:
2026-09-04 01:01:46 +00:00
parent 9331373245
commit ca1b694aa9
5 changed files with 297 additions and 105 deletions

View File

@@ -73,26 +73,20 @@ func Plan(ctx context.Context, args []string, out io.Writer) error {
if err != nil {
return fmt.Errorf("plan: fingerprint semantic configuration for stage %q: %w", selectedStage.Name(), err)
}
action := decideStageAction(selectedStage, model, request.Force)
decision := decideStageExecution(recordedStageStatus(model, selectedStage.Name()), request.Force, nil)
var validation *stage.ResumeValidation
if action == stageActionSkip {
if decision.action == stageActionSkip {
checked, validationErr := evaluateStageResume(ctx, selectedStage, stageEnv, model, semanticConfig)
if validationErr != nil {
return fmt.Errorf("plan: validate resume for stage %q: %w", selectedStage.Name(), validationErr)
}
validation = checked
if checked != nil && !checked.Resumable {
at := time.Now().UTC()
model.MarkStageStale(selectedStage.Name(), at, checked.Reason)
if _, invalidationErr := invalidateDependentSucceededStagesWithReason(
model, selectedStage.Name(), at, staleReasonNotResumable,
); invalidationErr != nil {
return fmt.Errorf("plan: model resume invalidation for stage %q: %w", selectedStage.Name(), invalidationErr)
}
action = stageActionRun
}
decision = decideStageExecution(recordedStageStatus(model, selectedStage.Name()), request.Force, checked)
}
if action == stageActionRun && selectedStage.Name() == "analyze" {
if _, err := applyPreExecutionDecision(model, selectedStage.Name(), decision, time.Now().UTC()); err != nil {
return fmt.Errorf("plan: model resume invalidation for stage %q: %w", selectedStage.Name(), err)
}
if decision.action == stageActionRun && selectedStage.Name() == "analyze" {
if validator, ok := selectedStage.(stage.ResumeValidator); ok {
checked, validationErr := validator.ValidateResume(ctx, stageEnv, model)
if validationErr != nil {
@@ -102,12 +96,12 @@ func Plan(ctx context.Context, args []string, out io.Writer) error {
validation = &checked
}
}
if action == stageActionRun {
if decision.action == stageActionRun {
runCount++
} else {
skipCount++
}
if _, err := fmt.Fprintf(out, "%s: %s\n", selectedStage.Name(), action); err != nil {
if _, err := fmt.Fprintf(out, "%s: %s\n", selectedStage.Name(), decision.action); err != nil {
return err
}
if validation != nil && validation.Analyze != nil {
@@ -115,8 +109,8 @@ func Plan(ctx context.Context, args []string, out io.Writer) error {
return err
}
}
if action == stageActionRun {
if err := modelPlannedStageRun(model, selectedStage, cfg, request.Force, semanticConfig); err != nil {
if decision.action == stageActionRun {
if err := modelPlannedStageRun(model, selectedStage, cfg, decision, semanticConfig); err != nil {
return fmt.Errorf("plan: model stage %q: %w", selectedStage.Name(), err)
}
}
@@ -149,40 +143,23 @@ func modelPlannedStageRun(
model *manifest.Manifest,
selectedStage stage.Stage,
cfg *config.Config,
force bool,
decision preExecutionDecision,
semanticConfig *manifest.SemanticConfigFingerprint,
) error {
prior := capturePriorStageOutcome(model, selectedStage.Name())
at := time.Now().UTC()
model.MarkStageRunning(selectedStage.Name(), at)
if force {
if _, err := invalidateDependentSucceededStagesWithReason(
model, selectedStage.Name(), at, staleReasonForcedReplacement,
); err != nil {
return err
}
if _, err := beginStageAttempt(model, selectedStage.Name(), decision, at); err != nil {
return err
}
if reason := plannedSelfSkipReason(selectedStage.Name(), cfg); reason != "" {
model.MarkStageSkipped(selectedStage.Name(), at, reason)
setSessionStageSemanticConfig(model, selectedStage.Name(), semanticConfig)
if !prior.isSameSelfSkip(reason) {
_, err := invalidateDependentSucceededStagesWithReason(
model, selectedStage.Name(), at, staleReasonSelfSkip,
)
return err
}
return nil
_, err := applyCompletedStageOutcome(model, selectedStage.Name(), &stage.StageResult{
Disposition: stage.StageDispositionSkipped,
SkipReason: reason,
}, nil, semanticConfig, prior, at)
return err
}
model.MarkStageSucceeded(selectedStage.Name(), at, nil)
setSessionStageSemanticConfig(model, selectedStage.Name(), semanticConfig)
if !prior.exists || prior.status != manifest.StatusSucceeded {
if _, err := invalidateDependentSucceededStagesWithReason(
model, selectedStage.Name(), at, staleReasonChangedResult,
); err != nil {
return err
}
}
return nil
_, err := applyCompletedStageOutcome(model, selectedStage.Name(), &stage.StageResult{}, nil, semanticConfig, prior, at)
return err
}
func plannedSelfSkipReason(stageName string, cfg *config.Config) string {