Enforce bounded run prerequisites

This commit is contained in:
2026-08-29 18:17:05 +00:00
parent 3bcf2c08dd
commit 966b95b176
11 changed files with 509 additions and 32 deletions

View File

@@ -44,6 +44,13 @@ type RunSummary struct {
var executeStagesFn = executeStages
func executeStages(ctx context.Context, cfg *config.Config, stages []stage.Stage, opts RunOptions) (summary *RunSummary, resultErr error) {
var prerequisiteStore manifest.Store
if opts.Env != nil {
prerequisiteStore = opts.Env.ManifestStore
}
if err := inspectBoundedPrerequisites(ctx, cfg, opts.Plan, prerequisiteStore); err != nil {
return nil, fmt.Errorf("validate bounded run prerequisites: %w", err)
}
effectiveArtifacts := opts.EffectiveArtifacts
if !effectiveArtifacts.Resolved() && cfg != nil && cfg.Pipeline != nil && cfg.Pipeline.Scriptorium != nil {
var err error
@@ -170,7 +177,7 @@ func executeStages(ctx context.Context, cfg *config.Config, stages []stage.Stage
fmt.Errorf("load secrets from files: %w", err),
)
}
if env.WhisperX == nil {
if env.WhisperX == nil && stagesContainAny(stages, "transcribe") {
client, err := buildDefaultWhisperXClient(env.Config)
if err != nil {
return nil, persistTerminalFailure(
@@ -180,7 +187,7 @@ func executeStages(ctx context.Context, cfg *config.Config, stages []stage.Stage
}
env.WhisperX = client
}
if env.Seriatim == nil {
if env.Seriatim == nil && stagesContainAny(stages, "merge", "normalize", "trim", "render") {
runner, err := buildDefaultSeriatimRunner(env.Config)
if err != nil {
return nil, persistTerminalFailure(
@@ -190,7 +197,7 @@ func executeStages(ctx context.Context, cfg *config.Config, stages []stage.Stage
}
env.Seriatim = runner
}
if env.Audita == nil {
if env.Audita == nil && stagesContainAny(stages, "polish") {
runner, err := buildDefaultAuditaRunner(env.Config)
if err != nil {
return nil, persistTerminalFailure(
@@ -203,7 +210,7 @@ func executeStages(ctx context.Context, cfg *config.Config, stages []stage.Stage
if env.Notarius == nil && needsNotariusForRun(env.Config, stages) {
env.Notarius = notarius.NewSubprocessRunner()
}
if env.Scriptorium == nil {
if env.Scriptorium == nil && stagesContainAny(stages, "trim", "analyze") {
env.Scriptorium = scriptorium.NewSubprocessRunner()
}
if env.ObjectStore == nil && needsObjectStoreForRun(env.Config, stages, effectiveArtifacts) {
@@ -233,7 +240,7 @@ func executeStages(ctx context.Context, cfg *config.Config, stages []stage.Stage
return config.MergePublishLockRules(staticLocks, remote.Locks), nil
}
}
if env.Notifier == nil {
if env.Notifier == nil && stagesContainAny(stages, "notify") {
env.Notifier = &notify.NoopSender{}
}
@@ -426,11 +433,13 @@ func executeStages(ctx context.Context, cfg *config.Config, stages []stage.Stage
// The run record lives inside the run work directory, which cleanup may
// remove. Persist its completed publishing result before cleanup starts so a
// successful deletion cannot be undone by a later diagnostic write.
if err := runPostPublishCleanup(ctx, env, manifestPath, m, executed); err != nil {
return nil, persistPostPublishCleanupFailure(
ctx, env.ManifestStore, manifestPath, m,
fmt.Errorf("post-publish cleanup incomplete: %w", err),
)
if containsStage(executed, "publish") {
if err := runPostPublishCleanup(ctx, env, manifestPath, m, executed); err != nil {
return nil, persistPostPublishCleanupFailure(
ctx, env.ManifestStore, manifestPath, m,
fmt.Errorf("post-publish cleanup incomplete: %w", err),
)
}
}
return &RunSummary{
@@ -444,6 +453,22 @@ func executeStages(ctx context.Context, cfg *config.Config, stages []stage.Stage
}, nil
}
func stagesContainAny(stages []stage.Stage, names ...string) bool {
wanted := make(map[string]struct{}, len(names))
for _, name := range names {
wanted[name] = struct{}{}
}
for _, candidate := range stages {
if candidate == nil {
continue
}
if _, ok := wanted[candidate.Name()]; ok {
return true
}
}
return false
}
func persistPostPublishCleanupFailure(
ctx context.Context,
sessionStore manifest.Store,