package stage import ( "context" "log/slog" "strings" "unicode/utf8" "gitea.maximumdirect.net/eric/narratio/internal/adapters/audita" "gitea.maximumdirect.net/eric/narratio/internal/adapters/notarius" "gitea.maximumdirect.net/eric/narratio/internal/adapters/notify" "gitea.maximumdirect.net/eric/narratio/internal/adapters/scriptorium" "gitea.maximumdirect.net/eric/narratio/internal/adapters/seriatim" "gitea.maximumdirect.net/eric/narratio/internal/adapters/storage" "gitea.maximumdirect.net/eric/narratio/internal/adapters/whisperx" "gitea.maximumdirect.net/eric/narratio/internal/artifacts" "gitea.maximumdirect.net/eric/narratio/internal/config" "gitea.maximumdirect.net/eric/narratio/internal/manifest" ) // Env is the shared dependency container visible to stages. type Env struct { Config *config.Config SelectedArtifactKeys []string EffectiveArtifacts artifacts.EffectiveArtifactSet ArtifactStore artifacts.Store ManifestStore manifest.Store Logger *slog.Logger WhisperX whisperx.Client Seriatim seriatim.Runner Audita audita.Runner Notarius notarius.Runner Scriptorium scriptorium.Runner ObjectStore storage.ObjectStore Notifier notify.Sender // RevalidatePublishLocks returns the effective lock set immediately before a // publish commit selects a new remote snapshot. RevalidatePublishLocks func(context.Context) ([]config.PublishLockRule, error) } // Stage is the pipeline unit contract. type Stage interface { Name() string Run(ctx context.Context, env *Env, m *manifest.Manifest) (*StageResult, error) } const maxResumeReasonLength = 512 // ResumeValidation reports whether a previously succeeded stage can be reused. type ResumeValidation struct { Resumable bool Reason string } // Normalized returns a result with a bounded reason and no reason on success. func (r ResumeValidation) Normalized() ResumeValidation { if r.Resumable { return Resumable() } return NonResumable(r.Reason) } // ResumeValidator is implemented by stages that validate persisted success before reuse. type ResumeValidator interface { ValidateResume(ctx context.Context, env *Env, m *manifest.Manifest) (ResumeValidation, error) } // Resumable reports a successful resume validation. func Resumable() ResumeValidation { return ResumeValidation{Resumable: true} } // NonResumable reports a bounded reason that persisted success must be rerun. func NonResumable(reason string) ResumeValidation { reason = strings.TrimSpace(reason) if reason == "" { reason = "persisted stage result is not reusable" } if len(reason) > maxResumeReasonLength { cutoff := maxResumeReasonLength for cutoff > 0 && !utf8.ValidString(reason[:cutoff]) { cutoff-- } reason = reason[:cutoff] } return ResumeValidation{Reason: reason} } // StageDisposition describes the outcome of a stage that returned without an error. type StageDisposition string const ( // StageDispositionSucceeded is the zero value so existing stages remain successful. StageDispositionSucceeded StageDisposition = "" StageDispositionSkipped StageDisposition = "skipped" ) // StageResult is the declared output of a stage execution. type StageResult struct { Disposition StageDisposition SkipReason string Outputs []artifacts.Ref Logs []string GeneratedConfigs []string Metadata map[string]any }