Centralized defaults into internal/config/defaults.go

This commit is contained in:
2026-05-17 07:53:51 -05:00
parent 539601bd16
commit a3986cf0d6
7 changed files with 111 additions and 52 deletions

View File

@@ -65,6 +65,8 @@ Optional secrets-from-files config:
YAML decoding is strict (`KnownFields(true)`), so unknown fields fail fast. YAML decoding is strict (`KnownFields(true)`), so unknown fields fail fast.
Maintainer note: application defaults are centralized in [`internal/config/defaults.go`](internal/config/defaults.go).
## Storage And Archive Foundations ## Storage And Archive Foundations
Narratio now includes configuration and path-model foundations for archive support, plus implemented prepare-stage S3 audio input. Narratio now includes configuration and path-model foundations for archive support, plus implemented prepare-stage S3 audio input.

View File

@@ -114,6 +114,7 @@ CLI pipeline config path resolution:
- when `--config` is omitted, Narratio searches defaults in order: - when `--config` is omitted, Narratio searches defaults in order:
- `/usr/local/etc/narratio/pipeline.yml` - `/usr/local/etc/narratio/pipeline.yml`
- `/etc/narratio/pipeline.yml` - `/etc/narratio/pipeline.yml`
- default values are centralized in `internal/config/defaults.go`
CLI session config path resolution: CLI session config path resolution:

View File

@@ -2,6 +2,8 @@ package artifacts
import ( import (
"path/filepath" "path/filepath"
"gitea.maximumdirect.net/eric/narratio/internal/config"
) )
// SessionPaths contains canonical local paths for one session work directory. // SessionPaths contains canonical local paths for one session work directory.
@@ -22,34 +24,33 @@ type SessionPaths struct {
// SessionWorkDir returns the work directory for one session. // SessionWorkDir returns the work directory for one session.
func SessionWorkDir(rootDir, sessionID string) string { func SessionWorkDir(rootDir, sessionID string) string {
return filepath.Join(rootDir, "work", sessionID) return filepath.Join(rootDir, config.PathWorkDirSegment, sessionID)
} }
// SessionRunWorkDir returns the campaign/session/run scoped local work directory. // SessionRunWorkDir returns the campaign/session/run scoped local work directory.
func SessionRunWorkDir(rootDir, campaign, sessionID, runID string) string { func SessionRunWorkDir(rootDir, campaign, sessionID, runID string) string {
return filepath.Join(rootDir, "work", campaign, sessionID, runID) return filepath.Join(rootDir, config.PathWorkDirSegment, campaign, sessionID, runID)
} }
// SessionSpoolAudioDir returns the campaign/session/run scoped local spool audio path. // SessionSpoolAudioDir returns the campaign/session/run scoped local spool audio path.
func SessionSpoolAudioDir(spoolRoot, campaign, sessionID, runID string) string { func SessionSpoolAudioDir(spoolRoot, campaign, sessionID, runID string) string {
return filepath.Join(spoolRoot, campaign, sessionID, runID, "audio") return filepath.Join(spoolRoot, campaign, sessionID, runID, config.PathAudioDirSegment)
} }
func buildSessionPaths(workspaceRoot, sessionID string) SessionPaths { func buildSessionPaths(workspaceRoot, sessionID string) SessionPaths {
root := SessionWorkDir(workspaceRoot, sessionID) root := SessionWorkDir(workspaceRoot, sessionID)
transcripts := filepath.Join(root, "transcripts")
return SessionPaths{ return SessionPaths{
WorkspaceRoot: workspaceRoot, WorkspaceRoot: workspaceRoot,
Root: root, Root: root,
InputsDir: filepath.Join(root, "inputs"), InputsDir: filepath.Join(root, config.PathInputsDirSegment),
AudioDir: filepath.Join(root, "audio"), AudioDir: filepath.Join(root, config.PathAudioDirSegment),
TranscriptsDir: transcripts, TranscriptsDir: filepath.Join(root, config.PathTranscriptsSegment),
TranscriptsRawDir: filepath.Join(transcripts, "raw"), TranscriptsRawDir: filepath.Join(root, filepath.FromSlash(config.PathTranscriptsRaw)),
TranscriptsTrimmedDir: filepath.Join(transcripts, "trimmed"), TranscriptsTrimmedDir: filepath.Join(root, filepath.FromSlash(config.PathTranscriptsTrimmed)),
ArtifactsDir: filepath.Join(root, "artifacts"), ArtifactsDir: filepath.Join(root, config.PathArtifactsDirSegment),
ConfigDir: filepath.Join(root, "config"), ConfigDir: filepath.Join(root, config.PathConfigDirSegment),
LogsDir: filepath.Join(root, "logs"), LogsDir: filepath.Join(root, config.PathLogsDirSegment),
ManifestPath: filepath.Join(root, "manifest.json"), ManifestPath: filepath.Join(root, config.PathManifestFile),
LockPath: filepath.Join(root, ".lock"), LockPath: filepath.Join(root, config.PathLockFile),
} }
} }

View File

@@ -3,6 +3,8 @@ package artifacts
import ( import (
"path" "path"
"strings" "strings"
"gitea.maximumdirect.net/eric/narratio/internal/config"
) )
// S3SessionPrefix builds the canonical S3 session prefix. // S3SessionPrefix builds the canonical S3 session prefix.
@@ -10,9 +12,9 @@ import (
func S3SessionPrefix(rootPrefix, campaign, sessionID string) string { func S3SessionPrefix(rootPrefix, campaign, sessionID string) string {
prefix := path.Join( prefix := path.Join(
cleanS3PathPart(rootPrefix), cleanS3PathPart(rootPrefix),
"campaigns", config.S3CampaignsSegment,
cleanS3PathPart(campaign), cleanS3PathPart(campaign),
"sessions", config.S3SessionsSegment,
cleanS3PathPart(sessionID), cleanS3PathPart(sessionID),
) )
return ensureS3TrailingSlash(prefix) return ensureS3TrailingSlash(prefix)
@@ -21,7 +23,7 @@ func S3SessionPrefix(rootPrefix, campaign, sessionID string) string {
// S3RunPrefix builds the canonical S3 run prefix. // S3RunPrefix builds the canonical S3 run prefix.
// Format: {session_prefix}/runs/{run_id}/ // Format: {session_prefix}/runs/{run_id}/
func S3RunPrefix(sessionPrefix, runID string) string { func S3RunPrefix(sessionPrefix, runID string) string {
prefix := path.Join(strings.TrimSuffix(cleanS3Key(sessionPrefix), "/"), "runs", cleanS3PathPart(runID)) prefix := path.Join(strings.TrimSuffix(cleanS3Key(sessionPrefix), "/"), config.S3RunsSegment, cleanS3PathPart(runID))
return ensureS3TrailingSlash(prefix) return ensureS3TrailingSlash(prefix)
} }
@@ -35,13 +37,13 @@ func S3AudioPrefix(sessionPrefix, audioPrefix string) string {
// S3CurrentManifestKey returns the current manifest pointer key. // S3CurrentManifestKey returns the current manifest pointer key.
// Format: {session_prefix}/current/manifest.json // Format: {session_prefix}/current/manifest.json
func S3CurrentManifestKey(sessionPrefix string) string { func S3CurrentManifestKey(sessionPrefix string) string {
return path.Join(strings.TrimSuffix(cleanS3Key(sessionPrefix), "/"), "current", "manifest.json") return path.Join(strings.TrimSuffix(cleanS3Key(sessionPrefix), "/"), config.S3CurrentSegment, config.S3ManifestFile)
} }
// S3CurrentRunPointerKey returns the current run pointer key. // S3CurrentRunPointerKey returns the current run pointer key.
// Format: {session_prefix}/current/run_id.txt // Format: {session_prefix}/current/run_id.txt
func S3CurrentRunPointerKey(sessionPrefix string) string { func S3CurrentRunPointerKey(sessionPrefix string) string {
return path.Join(strings.TrimSuffix(cleanS3Key(sessionPrefix), "/"), "current", "run_id.txt") return path.Join(strings.TrimSuffix(cleanS3Key(sessionPrefix), "/"), config.S3CurrentSegment, config.S3RunIDFile)
} }
// S3PromotedArtifactKey returns the destination key for one promoted artifact. // S3PromotedArtifactKey returns the destination key for one promoted artifact.

View File

@@ -10,8 +10,69 @@ const (
DefaultSessionConfigPathEtc = "/etc/narratio/session.yml" DefaultSessionConfigPathEtc = "/etc/narratio/session.yml"
DefaultS3AccessKeyIDEnv = "OBJECT_STORAGE_KEY_ID" DefaultS3AccessKeyIDEnv = "OBJECT_STORAGE_KEY_ID"
DefaultS3SecretAccessKeyEnv = "OBJECT_STORAGE_KEY" DefaultS3SecretAccessKeyEnv = "OBJECT_STORAGE_KEY"
DefaultStorageS3RootPrefix = "dnd"
DefaultSpoolRoot = "/var/spool/narratio"
DefaultWhisperXLanguage = "en"
DefaultWhisperXTimeout = "30m"
DefaultWhisperXRetryDelay = "2s"
DefaultWhisperXConcurrency = 2
DefaultWhisperXRetries = 3
DefaultSeriatimBinary = "seriatim"
DefaultSeriatimTimeout = "10m"
DefaultSeriatimOutputSchema = "seriatim-intermediate"
DefaultSeriatimCoalesceGap = 3.0
DefaultSeriatimReport = true
DefaultAuditaBinary = "audita"
DefaultAuditaTimeout = "3h"
DefaultAuditaReport = true
DefaultScriptoriumBinary = "scriptorium"
DefaultScriptoriumTimeout = "10m"
DefaultTrimBoundsTimeout = "10m"
DefaultTrimSeriatimReport = false
DefaultNormalizeOutputPath = "transcripts/normalized.json"
DefaultNormalizeOutputSchema = "seriatim-intermediate"
DefaultNormalizeReport = true
DefaultArchiveEnabled = true
DefaultArchiveUploadRun = true
PathWorkDirSegment = "work"
PathInputsDirSegment = "inputs"
PathAudioDirSegment = "audio"
PathTranscriptsSegment = "transcripts"
PathTranscriptsRaw = "transcripts/raw"
PathTranscriptsTrimmed = "transcripts/trimmed"
PathArtifactsDirSegment = "artifacts"
PathConfigDirSegment = "config"
PathLogsDirSegment = "logs"
PathManifestFile = "manifest.json"
PathLockFile = ".lock"
PathTranscriptMerged = "transcripts/merged.json"
PathTranscriptProcessed = "transcripts/processed.json"
PathTranscriptNormalized = "transcripts/normalized.json"
PathTranscriptTrimmed = "transcripts/trimmed.json"
S3CampaignsSegment = "campaigns"
S3SessionsSegment = "sessions"
S3RunsSegment = "runs"
S3CurrentSegment = "current"
S3ManifestFile = "manifest.json"
S3RunIDFile = "run_id.txt"
) )
// DefaultArchivePromoteArtifacts defines the default archive promotion rules.
// Callers should copy this slice before mutating.
var DefaultArchivePromoteArtifacts = []ArchivePromotionRule{
{From: PathTranscriptTrimmed, To: PathTranscriptTrimmed},
{From: "artifacts/session_recap.md", To: "artifacts/session_recap.md"},
}
// DefaultPipelineConfigSearchPaths defines the default search order for // DefaultPipelineConfigSearchPaths defines the default search order for
// pipeline.yml when callers do not provide an explicit path. // pipeline.yml when callers do not provide an explicit path.
// //

View File

@@ -174,7 +174,7 @@ func applyStorageDefaults(cfg *StorageConfig) {
cfg.S3 = &StorageS3Config{} cfg.S3 = &StorageS3Config{}
} }
if cfg.S3.RootPrefix == "" { if cfg.S3.RootPrefix == "" {
cfg.S3.RootPrefix = "dnd" cfg.S3.RootPrefix = DefaultStorageS3RootPrefix
} }
if cfg.S3.AccessKeyIDEnv == "" { if cfg.S3.AccessKeyIDEnv == "" {
cfg.S3.AccessKeyIDEnv = DefaultS3AccessKeyIDEnv cfg.S3.AccessKeyIDEnv = DefaultS3AccessKeyIDEnv
@@ -189,7 +189,7 @@ func applySpoolDefaults(cfg *SpoolConfig) {
return return
} }
if cfg.Root == "" { if cfg.Root == "" {
cfg.Root = "/var/spool/narratio" cfg.Root = DefaultSpoolRoot
} }
} }
@@ -202,16 +202,13 @@ func applyArchiveDefaults(cfg **ArchiveConfig) {
} }
if (*cfg).Enabled == nil { if (*cfg).Enabled == nil {
(*cfg).Enabled = boolPtr(true) (*cfg).Enabled = boolPtr(DefaultArchiveEnabled)
} }
if (*cfg).UploadRun == nil { if (*cfg).UploadRun == nil {
(*cfg).UploadRun = boolPtr(true) (*cfg).UploadRun = boolPtr(DefaultArchiveUploadRun)
} }
if len((*cfg).PromoteArtifacts) == 0 { if len((*cfg).PromoteArtifacts) == 0 {
(*cfg).PromoteArtifacts = []ArchivePromotionRule{ (*cfg).PromoteArtifacts = append([]ArchivePromotionRule(nil), DefaultArchivePromoteArtifacts...)
{From: "transcripts/trimmed.json", To: "transcripts/trimmed.json", Required: boolPtr(true)},
{From: "artifacts/session_recap.md", To: "artifacts/session_recap.md", Required: boolPtr(true)},
}
} }
for i := range (*cfg).PromoteArtifacts { for i := range (*cfg).PromoteArtifacts {
if (*cfg).PromoteArtifacts[i].Required == nil { if (*cfg).PromoteArtifacts[i].Required == nil {
@@ -225,19 +222,19 @@ func applyWhisperXDefaults(cfg *WhisperXConfig) {
return return
} }
if cfg.Language == "" { if cfg.Language == "" {
cfg.Language = "en" cfg.Language = DefaultWhisperXLanguage
} }
if cfg.Timeout == "" { if cfg.Timeout == "" {
cfg.Timeout = "30m" cfg.Timeout = DefaultWhisperXTimeout
} }
if cfg.RetryDelay == "" { if cfg.RetryDelay == "" {
cfg.RetryDelay = "2s" cfg.RetryDelay = DefaultWhisperXRetryDelay
} }
if cfg.Concurrency == nil { if cfg.Concurrency == nil {
cfg.Concurrency = intPtr(2) cfg.Concurrency = intPtr(DefaultWhisperXConcurrency)
} }
if cfg.Retries == nil { if cfg.Retries == nil {
cfg.Retries = intPtr(3) cfg.Retries = intPtr(DefaultWhisperXRetries)
} }
} }
@@ -251,19 +248,19 @@ func applySeriatimDefaults(cfg *SeriatimConfig) {
return return
} }
if cfg.Binary == "" { if cfg.Binary == "" {
cfg.Binary = "seriatim" cfg.Binary = DefaultSeriatimBinary
} }
if cfg.Timeout == "" { if cfg.Timeout == "" {
cfg.Timeout = "10m" cfg.Timeout = DefaultSeriatimTimeout
} }
if cfg.OutputSchema == "" { if cfg.OutputSchema == "" {
cfg.OutputSchema = "seriatim-intermediate" cfg.OutputSchema = DefaultSeriatimOutputSchema
} }
if cfg.CoalesceGap == nil { if cfg.CoalesceGap == nil {
cfg.CoalesceGap = float64Ptr(3.0) cfg.CoalesceGap = float64Ptr(DefaultSeriatimCoalesceGap)
} }
if cfg.Report == nil { if cfg.Report == nil {
cfg.Report = boolPtr(true) cfg.Report = boolPtr(DefaultSeriatimReport)
} }
} }
@@ -272,13 +269,13 @@ func applyAuditaDefaults(cfg *AuditaConfig) {
return return
} }
if cfg.Binary == "" { if cfg.Binary == "" {
cfg.Binary = "audita" cfg.Binary = DefaultAuditaBinary
} }
if cfg.Timeout == "" { if cfg.Timeout == "" {
cfg.Timeout = "3h" cfg.Timeout = DefaultAuditaTimeout
} }
if cfg.Report == nil { if cfg.Report == nil {
cfg.Report = boolPtr(true) cfg.Report = boolPtr(DefaultAuditaReport)
} }
} }
@@ -287,10 +284,10 @@ func applyScriptoriumDefaults(cfg *ScriptoriumConfig) {
return return
} }
if cfg.Binary == "" { if cfg.Binary == "" {
cfg.Binary = "scriptorium" cfg.Binary = DefaultScriptoriumBinary
} }
if cfg.Timeout == "" { if cfg.Timeout == "" {
cfg.Timeout = "10m" cfg.Timeout = DefaultScriptoriumTimeout
} }
} }
@@ -299,10 +296,10 @@ func applyTrimDefaults(cfg *TrimConfig) {
return return
} }
if cfg.Bounds.Timeout == "" { if cfg.Bounds.Timeout == "" {
cfg.Bounds.Timeout = "10m" cfg.Bounds.Timeout = DefaultTrimBoundsTimeout
} }
if cfg.Seriatim.Report == nil { if cfg.Seriatim.Report == nil {
cfg.Seriatim.Report = boolPtr(false) cfg.Seriatim.Report = boolPtr(DefaultTrimSeriatimReport)
} }
} }
@@ -311,13 +308,13 @@ func applyNormalizeDefaults(cfg *NormalizeConfig) {
return return
} }
if cfg.OutputSchema == "" { if cfg.OutputSchema == "" {
cfg.OutputSchema = defaultNormalizeOutputSchema cfg.OutputSchema = DefaultNormalizeOutputSchema
} }
if cfg.OutputPath == "" && !cfg.outputPathWasSet() { if cfg.OutputPath == "" && !cfg.outputPathWasSet() {
cfg.OutputPath = defaultNormalizeOutputPath cfg.OutputPath = DefaultNormalizeOutputPath
} }
if cfg.Report == nil { if cfg.Report == nil {
cfg.Report = boolPtr(true) cfg.Report = boolPtr(DefaultNormalizeReport)
} }
} }

View File

@@ -6,11 +6,6 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
const (
defaultNormalizeOutputPath = "transcripts/normalized.json"
defaultNormalizeOutputSchema = "seriatim-intermediate"
)
// UnmarshalYAML tracks explicit normalize.output_path presence so validation can // UnmarshalYAML tracks explicit normalize.output_path presence so validation can
// distinguish omitted vs explicitly empty values. // distinguish omitted vs explicitly empty values.
func (cfg *NormalizeConfig) UnmarshalYAML(node *yaml.Node) error { func (cfg *NormalizeConfig) UnmarshalYAML(node *yaml.Node) error {