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

@@ -34,21 +34,89 @@ type priorStageOutcome struct {
outputs int
}
func decideStageAction(s stage.Stage, m *manifest.Manifest, force bool) stageAction {
// A succeeded aggregate record is the initial skip candidate. The runner and
// planner then let stage-owned resume validation refine that decision.
if !force && stageSucceeded(m, s.Name()) {
return stageActionSkip
}
return stageActionRun
type preExecutionDecision struct {
action stageAction
markSelectedStale bool
selectedStaleReason string
invalidationReason string
invalidateWhenStarted bool
}
func stageSucceeded(m *manifest.Manifest, name string) bool {
func decideStageExecution(status manifest.StageStatus, force bool, resume *stage.ResumeValidation) preExecutionDecision {
if force {
return preExecutionDecision{
action: stageActionRun,
invalidationReason: staleReasonForcedReplacement,
invalidateWhenStarted: true,
}
}
if status != manifest.StatusSucceeded {
return preExecutionDecision{action: stageActionRun}
}
if resume != nil && !resume.Resumable {
return preExecutionDecision{
action: stageActionRun,
markSelectedStale: true,
selectedStaleReason: resume.Reason,
invalidationReason: staleReasonNotResumable,
}
}
return preExecutionDecision{action: stageActionSkip}
}
func recordedStageStatus(m *manifest.Manifest, name string) manifest.StageStatus {
if m == nil || m.Stages == nil {
return false
return ""
}
sr := m.Stages[name]
return sr != nil && sr.Status == manifest.StatusSucceeded
if sr == nil {
return ""
}
return sr.Status
}
func applyPreExecutionDecision(m *manifest.Manifest, name string, decision preExecutionDecision, at time.Time) ([]string, error) {
if !decision.markSelectedStale {
return nil, nil
}
m.MarkStageStale(name, at, decision.selectedStaleReason)
return invalidateDependentSucceededStagesWithReason(m, name, at, decision.invalidationReason)
}
func beginStageAttempt(m *manifest.Manifest, name string, decision preExecutionDecision, at time.Time) ([]string, error) {
m.MarkStageRunning(name, at)
if !decision.invalidateWhenStarted {
return nil, nil
}
return invalidateDependentSucceededStagesWithReason(m, name, at, decision.invalidationReason)
}
func applyCompletedStageOutcome(
m *manifest.Manifest,
name string,
result *stage.StageResult,
outputs []manifest.ArtifactRecord,
semanticConfig *manifest.SemanticConfigFingerprint,
prior priorStageOutcome,
at time.Time,
) ([]string, error) {
if result != nil && result.Disposition == stage.StageDispositionSkipped {
m.MarkStageSkipped(name, at, result.SkipReason)
applyStageResultToManifest(m, name, result)
setSessionStageSemanticConfig(m, name, semanticConfig)
if prior.isSameSelfSkip(result.SkipReason) {
return nil, nil
}
return invalidateDependentSucceededStagesWithReason(m, name, at, staleReasonSelfSkip)
}
m.MarkStageSucceeded(name, at, outputs)
applyStageResultToManifest(m, name, result)
setSessionStageSemanticConfig(m, name, semanticConfig)
if prior.exists && prior.status == manifest.StatusSucceeded {
return nil, nil
}
return invalidateDependentSucceededStagesWithReason(m, name, at, staleReasonChangedResult)
}
func capturePriorStageOutcome(m *manifest.Manifest, name string) priorStageOutcome {