Centralize effective artifact selection
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/config"
|
||||
)
|
||||
|
||||
@@ -47,19 +48,55 @@ func (f *artifactSelectionFlag) Normalize() ([]string, error) {
|
||||
}
|
||||
|
||||
func validateSelectedArtifacts(cfg *config.Config, selected []string) error {
|
||||
if len(selected) == 0 {
|
||||
return nil
|
||||
}
|
||||
_, err := resolveEffectiveArtifacts(cfg, selected)
|
||||
return err
|
||||
}
|
||||
|
||||
func resolveEffectiveArtifacts(cfg *config.Config, selected []string) (artifacts.EffectiveArtifactSet, error) {
|
||||
if cfg == nil || cfg.Pipeline == nil || cfg.Pipeline.Scriptorium == nil {
|
||||
return fmt.Errorf("--artifacts requires pipeline.scriptorium.artifacts to be configured")
|
||||
if len(selected) == 0 {
|
||||
return artifacts.ResolveEffectiveArtifactSet(nil, nil)
|
||||
}
|
||||
return artifacts.EffectiveArtifactSet{}, fmt.Errorf("--artifacts requires pipeline.scriptorium.artifacts to be configured")
|
||||
}
|
||||
configured := cfg.Pipeline.Scriptorium.Artifacts
|
||||
if len(configured) == 0 {
|
||||
return fmt.Errorf("--artifacts requires at least one configured artifact in pipeline.scriptorium.artifacts")
|
||||
configured := artifacts.ConfiguredArtifactDefinitions(cfg.Pipeline.Scriptorium.Artifacts)
|
||||
if len(selected) > 0 && len(configured) == 0 {
|
||||
return artifacts.EffectiveArtifactSet{}, fmt.Errorf("--artifacts requires at least one configured artifact in pipeline.scriptorium.artifacts")
|
||||
}
|
||||
for _, name := range selected {
|
||||
if _, ok := configured[name]; !ok {
|
||||
return fmt.Errorf("--artifacts includes unknown artifact %q", name)
|
||||
effective, err := artifacts.ResolveEffectiveArtifactSet(configured, selected)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "is not configured") {
|
||||
return artifacts.EffectiveArtifactSet{}, fmt.Errorf("--artifacts includes unknown artifact %q", selectedArtifactName(err))
|
||||
}
|
||||
return artifacts.EffectiveArtifactSet{}, err
|
||||
}
|
||||
if err := validateEffectiveArtifactConfiguration(cfg.Pipeline.Scriptorium.Artifacts, effective); err != nil {
|
||||
return artifacts.EffectiveArtifactSet{}, err
|
||||
}
|
||||
return effective, nil
|
||||
}
|
||||
|
||||
func selectedArtifactName(err error) string {
|
||||
message := err.Error()
|
||||
start := strings.Index(message, "\"")
|
||||
if start < 0 {
|
||||
return ""
|
||||
}
|
||||
end := strings.Index(message[start+1:], "\"")
|
||||
if end < 0 {
|
||||
return ""
|
||||
}
|
||||
return message[start+1 : start+1+end]
|
||||
}
|
||||
|
||||
func validateEffectiveArtifactConfiguration(configured map[string]config.ScriptoriumArtifactConfig, effective artifacts.EffectiveArtifactSet) error {
|
||||
for _, name := range effective.Keys() {
|
||||
artifactCfg := configured[name]
|
||||
if strings.TrimSpace(artifactCfg.PromptID) == "" {
|
||||
return fmt.Errorf("pipeline.scriptorium.artifacts.%s.prompt_id is required when selected", name)
|
||||
}
|
||||
if strings.TrimSpace(artifactCfg.OutputPath) == "" {
|
||||
return fmt.Errorf("pipeline.scriptorium.artifacts.%s.output_path is required when selected", name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -110,6 +110,20 @@ func TestValidateSelectedArtifacts(t *testing.T) {
|
||||
},
|
||||
selected: []string{"player_handout", "session_recap"},
|
||||
},
|
||||
{
|
||||
name: "selected disabled artifact must be executable",
|
||||
cfg: &config.Config{
|
||||
Pipeline: &config.PipelineConfig{
|
||||
Scriptorium: &config.ScriptoriumConfig{
|
||||
Artifacts: map[string]config.ScriptoriumArtifactConfig{
|
||||
"player_handout": {Enabled: false, OutputPath: "artifacts/player_handout.md"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
selected: []string{"player_handout"},
|
||||
wantErr: "pipeline.scriptorium.artifacts.player_handout.prompt_id is required when selected",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -14,21 +14,17 @@ import (
|
||||
)
|
||||
|
||||
func buildHelperArtifactCatalog(cfg *config.Config, m *manifest.Manifest) (*artifacts.ArtifactCatalog, error) {
|
||||
catalog := artifacts.NewArtifactCatalog()
|
||||
if err := catalog.RegisterBuiltIns(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configured := map[string]artifacts.ConfiguredArtifactDefinition{}
|
||||
configured := artifacts.ConfiguredArtifactDefinitions(nil)
|
||||
if cfg.Pipeline.Scriptorium != nil {
|
||||
for key, item := range cfg.Pipeline.Scriptorium.Artifacts {
|
||||
configured[key] = artifacts.ConfiguredArtifactDefinition{Enabled: item.Enabled, OutputPath: item.OutputPath}
|
||||
}
|
||||
}
|
||||
if err := catalog.RegisterConfiguredArtifacts(configured, nil); err != nil {
|
||||
return nil, err
|
||||
configured = artifacts.ConfiguredArtifactDefinitions(cfg.Pipeline.Scriptorium.Artifacts)
|
||||
}
|
||||
extractionDefinitions := artifacts.ExtractionDefinitionsFromConfig(cfg.Pipeline.Notarius)
|
||||
if err := catalog.RegisterExtractionArtifacts(extractionDefinitions); err != nil {
|
||||
effective, err := artifacts.ResolveEffectiveArtifactSet(configured, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
catalog, err := artifacts.BootstrapRuntimeCatalog(configured, effective, extractionDefinitions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paths := artifacts.NewLocalStore(cfg.Pipeline.Workspace.Root).SessionPathsFor(cfg.Session.Campaign, cfg.Session.SessionID)
|
||||
@@ -58,8 +54,10 @@ func writeArtifactList(out io.Writer, cfg *config.Config, catalog *artifacts.Art
|
||||
writeExtractionArtifactLine(out, entry.SourceID, state, entry.Provenance, lockSet)
|
||||
}
|
||||
fmt.Fprintln(out, "Previous-session:")
|
||||
for _, req := range artifacts.CollectPreviousArtifactRequirements(configuredScriptoriumArtifacts(cfg)) {
|
||||
fmt.Fprintf(out, "- %s required=%t\n", artifactpolicy.PreviousSessionSourceID(req.Name), req.Required)
|
||||
if effective, err := resolveEffectiveArtifacts(cfg, nil); err == nil {
|
||||
for _, req := range artifacts.CollectPreviousArtifactRequirements(configuredScriptoriumArtifacts(cfg), effective) {
|
||||
fmt.Fprintf(out, "- %s required=%t\n", artifactpolicy.PreviousSessionSourceID(req.Name), req.Required)
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(out, "Published:")
|
||||
for _, rule := range cfg.Pipeline.Publish.Outputs {
|
||||
|
||||
@@ -55,7 +55,12 @@ func SessionValidate(ctx context.Context, args []string, out io.Writer) error {
|
||||
}
|
||||
}
|
||||
|
||||
requirements := artifacts.CollectPreviousArtifactRequirements(configuredScriptoriumArtifacts(cfg))
|
||||
effective, effectiveErr := resolveEffectiveArtifacts(cfg, nil)
|
||||
if effectiveErr != nil {
|
||||
findings = append(findings, errorFinding("config", effectiveErr.Error()))
|
||||
return renderFindings(out, cfg.Session.Campaign, cfg.Session.SessionID, findings)
|
||||
}
|
||||
requirements := artifacts.CollectPreviousArtifactRequirements(configuredScriptoriumArtifacts(cfg), effective)
|
||||
previous := inspectPreviousArtifactReadiness(ctx, cfg, store, requirements)
|
||||
if len(previous.Requirements) == 0 {
|
||||
findings = append(findings, okFinding("previous", "no previous-session artifacts required"))
|
||||
|
||||
@@ -69,11 +69,15 @@ func Status(ctx context.Context, args []string, out io.Writer) error {
|
||||
}
|
||||
}
|
||||
writeStatusRemoteAudio(ctx, out, cfg, store, storeErr)
|
||||
effective, effectiveErr := resolveEffectiveArtifacts(cfg, nil)
|
||||
if effectiveErr != nil {
|
||||
return fmt.Errorf("status: resolve effective artifacts: %w", effectiveErr)
|
||||
}
|
||||
writeStatusPreviousArtifacts(out, inspectPreviousArtifactReadiness(
|
||||
ctx,
|
||||
cfg,
|
||||
store,
|
||||
artifacts.CollectPreviousArtifactRequirements(configuredScriptoriumArtifacts(cfg)),
|
||||
artifacts.CollectPreviousArtifactRequirements(configuredScriptoriumArtifacts(cfg), effective),
|
||||
))
|
||||
|
||||
lockChecks := inspectEffectiveLocks(ctx, cfg, store)
|
||||
|
||||
@@ -343,7 +343,11 @@ func buildPreviousCacheRestoreActions(
|
||||
if cfg == nil || cfg.Pipeline == nil || cfg.Pipeline.Scriptorium == nil {
|
||||
return nil, nil
|
||||
}
|
||||
requirements := artifacts.CollectPreviousArtifactRequirements(cfg.Pipeline.Scriptorium.Artifacts)
|
||||
effective, err := resolveEffectiveArtifacts(cfg, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requirements := artifacts.CollectPreviousArtifactRequirements(cfg.Pipeline.Scriptorium.Artifacts, effective)
|
||||
if len(requirements) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -40,14 +40,15 @@ func Run(ctx context.Context, args []string, out io.Writer) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("run: invalid --artifacts: %w", err)
|
||||
}
|
||||
if err := validateSelectedArtifacts(cfg, normalizedArtifacts); err != nil {
|
||||
effectiveArtifacts, err := resolveEffectiveArtifacts(cfg, normalizedArtifacts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("run: %w", err)
|
||||
}
|
||||
|
||||
stages := BuildFullPlan()
|
||||
summary, err := executeStagesFn(ctx, cfg, stages, RunOptions{
|
||||
Force: force,
|
||||
SelectedArtifacts: normalizedArtifacts,
|
||||
Force: force,
|
||||
SelectedArtifacts: normalizedArtifacts,
|
||||
EffectiveArtifacts: effectiveArtifacts,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("run: %w", err)
|
||||
|
||||
@@ -216,13 +216,14 @@ func runSingleStageCommand(ctx context.Context, req singleStageCommand) (*RunSum
|
||||
if err := config.Validate(cfg); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", req.CommandName, err)
|
||||
}
|
||||
if err := validateSelectedArtifacts(cfg, req.SelectedArtifacts); err != nil {
|
||||
effectiveArtifacts, err := resolveEffectiveArtifacts(cfg, req.SelectedArtifacts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", req.CommandName, err)
|
||||
}
|
||||
|
||||
summary, err := executeStagesFn(ctx, cfg, stages, RunOptions{
|
||||
Force: req.Force,
|
||||
SelectedArtifacts: req.SelectedArtifacts,
|
||||
Force: req.Force,
|
||||
SelectedArtifacts: req.SelectedArtifacts,
|
||||
EffectiveArtifacts: effectiveArtifacts,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", req.CommandName, err)
|
||||
|
||||
@@ -23,10 +23,11 @@ import (
|
||||
)
|
||||
|
||||
type RunOptions struct {
|
||||
Force bool
|
||||
SelectedArtifacts []string
|
||||
Env *Env
|
||||
RunManifestStore manifest.RunStore
|
||||
Force bool
|
||||
SelectedArtifacts []string
|
||||
EffectiveArtifacts artifacts.EffectiveArtifactSet
|
||||
Env *Env
|
||||
RunManifestStore manifest.RunStore
|
||||
}
|
||||
|
||||
type RunSummary struct {
|
||||
@@ -42,6 +43,21 @@ type RunSummary struct {
|
||||
var executeStagesFn = executeStages
|
||||
|
||||
func executeStages(ctx context.Context, cfg *config.Config, stages []stage.Stage, opts RunOptions) (summary *RunSummary, resultErr error) {
|
||||
effectiveArtifacts := opts.EffectiveArtifacts
|
||||
if !effectiveArtifacts.Resolved() && cfg != nil && cfg.Pipeline != nil && cfg.Pipeline.Scriptorium != nil {
|
||||
var err error
|
||||
effectiveArtifacts, err = resolveEffectiveArtifacts(cfg, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve effective artifacts: %w", err)
|
||||
}
|
||||
}
|
||||
if !effectiveArtifacts.Resolved() {
|
||||
var err error
|
||||
effectiveArtifacts, err = artifacts.ResolveEffectiveArtifactSet(nil, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve default effective artifacts: %w", err)
|
||||
}
|
||||
}
|
||||
runID, err := artifacts.NewRunID()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generate run id: %w", err)
|
||||
@@ -59,6 +75,7 @@ func executeStages(ctx context.Context, cfg *config.Config, stages []stage.Stage
|
||||
// Injected environments supply collaborators, never an alternate config.
|
||||
env.Config = cfg
|
||||
env.SelectedArtifactKeys = append([]string(nil), opts.SelectedArtifacts...)
|
||||
env.EffectiveArtifacts = effectiveArtifacts
|
||||
if env.ManifestStore == nil {
|
||||
env.ManifestStore = &manifest.LocalStore{}
|
||||
}
|
||||
@@ -188,7 +205,7 @@ func executeStages(ctx context.Context, cfg *config.Config, stages []stage.Stage
|
||||
if env.Scriptorium == nil {
|
||||
env.Scriptorium = scriptorium.NewSubprocessRunner()
|
||||
}
|
||||
if env.ObjectStore == nil && needsObjectStoreForRun(env.Config, stages) {
|
||||
if env.ObjectStore == nil && needsObjectStoreForRun(env.Config, stages, effectiveArtifacts) {
|
||||
objectStore, err := newCommandObjectStore(ctx, env.Config, nil)
|
||||
if err != nil {
|
||||
return nil, persistTerminalFailure(
|
||||
@@ -838,10 +855,24 @@ func manifestPathFor(cfg *config.Config) string {
|
||||
)
|
||||
}
|
||||
|
||||
func needsObjectStoreForRun(cfg *config.Config, stages []stage.Stage) bool {
|
||||
func needsObjectStoreForRun(cfg *config.Config, stages []stage.Stage, effectiveSets ...artifacts.EffectiveArtifactSet) bool {
|
||||
if cfg == nil || cfg.Pipeline == nil || cfg.Session == nil {
|
||||
return false
|
||||
}
|
||||
effective := artifacts.EffectiveArtifactSet{}
|
||||
if len(effectiveSets) > 0 {
|
||||
effective = effectiveSets[0]
|
||||
}
|
||||
if !effective.Resolved() && cfg.Pipeline.Scriptorium != nil {
|
||||
var err error
|
||||
effective, err = artifacts.ResolveEffectiveArtifactSet(
|
||||
artifacts.ConfiguredArtifactDefinitions(cfg.Pipeline.Scriptorium.Artifacts),
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
stageRequested := func(name string) bool {
|
||||
for _, s := range stages {
|
||||
if s != nil && s.Name() == name {
|
||||
@@ -855,7 +886,7 @@ func needsObjectStoreForRun(cfg *config.Config, stages []stage.Stage) bool {
|
||||
return true
|
||||
}
|
||||
if stageRequested("prepare") {
|
||||
requirements := artifacts.CollectPreviousArtifactRequirements(configuredScriptoriumArtifacts(cfg))
|
||||
requirements := artifacts.CollectPreviousArtifactRequirements(configuredScriptoriumArtifacts(cfg), effective)
|
||||
if len(requirements) > 0 && strings.TrimSpace(cfg.Session.PreviousSessionID) != "" {
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user