Add Notarius reference configuration vocabulary

This commit is contained in:
2026-08-29 14:55:24 +00:00
parent 42ed81cbe1
commit 5831c0c9e6
12 changed files with 476 additions and 42 deletions

View File

@@ -12,6 +12,7 @@ import (
"gitea.maximumdirect.net/eric/narratio/internal/artifactmodel"
"gitea.maximumdirect.net/eric/narratio/internal/artifactpolicy"
"gitea.maximumdirect.net/eric/narratio/internal/notariusref"
"gitea.maximumdirect.net/eric/narratio/internal/pathsafe"
)
@@ -39,7 +40,7 @@ func Validate(cfg *Config) error {
if err := validateSession(cfg.Session); err != nil {
return fmt.Errorf("session config %q invalid: %w", shortName(cfg.SessionPath, "session.yml"), err)
}
if err := validateCrossConfig(cfg.Pipeline, cfg.Session); err != nil {
if err := validateCrossConfig(cfg.Pipeline, cfg.Session, cfg.StableInputs); err != nil {
return fmt.Errorf("pipeline/session config invalid: %w", err)
}
@@ -71,6 +72,9 @@ func validateCampaign(cfg *CampaignConfig) error {
if strings.TrimSpace(cfg.Inputs.PartyFile) == "" {
return fmt.Errorf("campaign.inputs.party_file is required")
}
if cfg.Inputs.SpellCatalogFile != "" && strings.TrimSpace(cfg.Inputs.SpellCatalogFile) == "" {
return fmt.Errorf("campaign.inputs.spell_catalog_file must be non-empty when provided")
}
return nil
}
@@ -547,6 +551,39 @@ func validateNotarius(cfg *NotariusConfig, scriptorium *ScriptoriumConfig) error
if strings.TrimSpace(cfg.WorkingDirectory) == "" {
return fmt.Errorf("pipeline.notarius.working_directory is required when pipeline.notarius.enabled is true")
}
if len(cfg.References) > MaxNotariusReferenceBindings {
return fmt.Errorf("pipeline.notarius.references must contain at most %d bindings", MaxNotariusReferenceBindings)
}
referenceKeys := make([]string, 0, len(cfg.References))
for selector := range cfg.References {
referenceKeys = append(referenceKeys, selector)
}
sort.Strings(referenceKeys)
var normalizedReferences map[string]string
if cfg.References != nil {
normalizedReferences = make(map[string]string, len(cfg.References))
}
referenceOwners := make(map[string]string, len(cfg.References))
for _, rawSelector := range referenceKeys {
selector, err := notariusref.NormalizeSelector(rawSelector)
if err != nil {
return fmt.Errorf("pipeline.notarius.references selector %q is invalid: %w", rawSelector, err)
}
if previous, ok := referenceOwners[selector]; ok {
return fmt.Errorf("pipeline.notarius.references selectors %q and %q normalize to %q", previous, rawSelector, selector)
}
referenceOwners[selector] = rawSelector
source := strings.TrimSpace(cfg.References[rawSelector])
if source == "" {
return fmt.Errorf("pipeline.notarius.references.%s source is required", selector)
}
if _, ok := artifactpolicy.DescribePreparedInputSource(source); !ok {
return fmt.Errorf("pipeline.notarius.references.%s source %q is not a supported prepared input source", selector, source)
}
normalizedReferences[selector] = source
}
reservedSources := map[string]string{}
for _, spec := range artifactmodel.RuntimeTranscriptArtifacts() {
@@ -613,6 +650,7 @@ func validateNotarius(cfg *NotariusConfig, scriptorium *ScriptoriumConfig) error
cfg.PipelineID = strings.TrimSpace(cfg.PipelineID)
cfg.Timeout = strings.TrimSpace(cfg.Timeout)
cfg.WorkingDirectory = filepath.Clean(cfg.WorkingDirectory)
cfg.References = normalizedReferences
cfg.Outputs = normalizedOutputs
return nil
}
@@ -762,6 +800,9 @@ func validateSession(cfg *SessionConfig) error {
if strings.TrimSpace(cfg.Inputs.PartyFile) == "" {
return fmt.Errorf("session.inputs.party_file is required")
}
if cfg.Inputs.SpellCatalogFile != "" && strings.TrimSpace(cfg.Inputs.SpellCatalogFile) == "" {
return fmt.Errorf("session.inputs.spell_catalog_file must be non-empty when provided")
}
hasAudioDir := strings.TrimSpace(cfg.Inputs.AudioDir) != ""
hasAudioFiles := len(cfg.Inputs.AudioFiles) > 0
@@ -797,10 +838,17 @@ func validateSessionIdentifier(fieldName, value string, required bool) error {
return nil
}
func validateCrossConfig(pipeline *PipelineConfig, session *SessionConfig) error {
func validateCrossConfig(pipeline *PipelineConfig, session *SessionConfig, stableInputs ResolvedStableInputs) error {
if pipeline == nil || session == nil {
return nil
}
if pipeline.Notarius != nil && pipeline.Notarius.Enabled {
for selector, source := range pipeline.Notarius.References {
if source == artifactpolicy.SourceInputSpellCatalog && strings.TrimSpace(stableInputs.SpellCatalogFile.Path) == "" {
return fmt.Errorf("pipeline.notarius.references.%s requires campaign.inputs.spell_catalog_file or session.inputs.spell_catalog_file", selector)
}
}
}
audioS3Enabled := session.Inputs.AudioS3 != nil
publishUploadEnabled := publishUploadConfiguredForS3(pipeline)