package app import ( "context" "fmt" "time" "gitea.maximumdirect.net/eric/narratio/internal/artifacts" "gitea.maximumdirect.net/eric/narratio/internal/config" "gitea.maximumdirect.net/eric/narratio/internal/manifest" "gitea.maximumdirect.net/eric/narratio/internal/stage" ) type stageAction string const ( stageActionRun stageAction = "run" stageActionSkip stageAction = "skip" ) type stageDecision struct { Stage stage.Stage Action stageAction } func decideStageActions(stages []stage.Stage, m *manifest.Manifest, force bool) []stageDecision { out := make([]stageDecision, 0, len(stages)) for _, s := range stages { action := stageActionRun // TODO: incorporate stale detection once checksum/input change tracking is implemented. if !force && stageSucceeded(m, s.Name()) { action = stageActionSkip } out = append(out, stageDecision{ Stage: s, Action: action, }) } return out } func stageSucceeded(m *manifest.Manifest, name string) bool { if m == nil || m.Stages == nil { return false } sr := m.Stages[name] return sr != nil && sr.Status == manifest.StatusSucceeded } func loadManifestIfPresent(ctx context.Context, cfg *config.Config) (*manifest.Manifest, error) { path := artifacts.SessionManifestPathForCampaign( cfg.Pipeline.Workspace.Root, cfg.Session.Campaign, cfg.Session.SessionID, ) exists, err := fileExists(path) if err != nil { return nil, fmt.Errorf("check manifest %q: %w", path, err) } if !exists { return nil, nil } store := &manifest.LocalStore{} m, err := store.Load(ctx, path) if err != nil { return nil, fmt.Errorf("load manifest %q: %w", path, err) } return m, nil } func canonicalStageNames() []string { all := stage.All() out := make([]string, 0, len(all)) for _, s := range all { if s == nil { continue } out = append(out, s.Name()) } return out } func downstreamStageNames(stageName string) []string { names := canonicalStageNames() for i, name := range names { if name != stageName { continue } return append([]string(nil), names[i+1:]...) } return nil } func invalidateDownstreamSucceededStages(m *manifest.Manifest, upstreamStage string, at time.Time) []string { return invalidateDownstreamSucceededStagesWithReason(m, upstreamStage, at, "upstream stage rerun with force") } func invalidateDownstreamSucceededStagesWithReason(m *manifest.Manifest, upstreamStage string, at time.Time, reason string) []string { if m == nil || m.Stages == nil { return nil } invalidated := make([]string, 0) for _, downstream := range downstreamStageNames(upstreamStage) { sr := m.Stages[downstream] if sr == nil || sr.Status != manifest.StatusSucceeded { continue } m.MarkStageStale(downstream, at, reason) invalidated = append(invalidated, downstream) } return invalidated }