Files
narratio/internal/stage/stage.go

111 lines
3.2 KiB
Go

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
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
}
// IODecl declares the intended input/output artifact kinds for a stage.
type IODecl struct {
Inputs []artifacts.Ref
Outputs []artifacts.Ref
}
// Stage is the pipeline unit contract.
type Stage interface {
Name() string
Declares() IODecl
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
}