diff --git a/internal/app/operator_helpers.go b/internal/app/operator_helpers.go index 74796ee..d952d2a 100644 --- a/internal/app/operator_helpers.go +++ b/internal/app/operator_helpers.go @@ -239,13 +239,13 @@ func Status(ctx context.Context, args []string, out io.Writer) error { store, storeErr := objectStoreIfConfigured(ctx, cfg) if storeErr != nil { - fmt.Fprintf(out, "Remote archive: unavailable: %v\n", storeErr) + fmt.Fprintf(out, "Remote publish: unavailable: %v\n", storeErr) } else if store != nil { current, err := discoverRemoteCurrentStateFn(ctx, cfg, store) if err != nil { - fmt.Fprintf(out, "Remote archive: missing or unavailable: %v\n", err) + fmt.Fprintf(out, "Remote publish: missing or unavailable: %v\n", err) } else { - fmt.Fprintf(out, "Remote archive: current run %s\n", current.RunID) + fmt.Fprintf(out, "Remote publish: current run %s\n", current.RunID) fmt.Fprintf(out, "Remote manifest: %s\n", current.CurrentManifestKey) } } @@ -261,15 +261,15 @@ func Status(ctx context.Context, args []string, out io.Writer) error { All: staticArchiveLocks(cfg), } } - promotedRemoteState := map[string]string{} + publishedRemoteState := map[string]string{} if store != nil { - promotedRemoteState = remotePromotionAvailability(ctx, cfg, store, catalog) + publishedRemoteState = remotePublishedOutputAvailability(ctx, cfg, store, catalog) } fmt.Fprintln(out, "Remote outputs:") - writeArtifactList(out, cfg, catalog, catalogLocks, promotedRemoteState) + writeArtifactList(out, cfg, catalog, catalogLocks, publishedRemoteState) } if err != nil { - fmt.Fprintf(out, "Archive locks: error: %v\n", err) + fmt.Fprintf(out, "Publish locks: error: %v\n", err) } else { writeLocks(out, cfg, locks) } @@ -437,11 +437,11 @@ func ArtifactsList(ctx context.Context, args []string, out io.Writer) error { if err != nil { return fmt.Errorf("artifacts list: %w", err) } - promotedRemoteState := map[string]string{} + publishedRemoteState := map[string]string{} if remote && store != nil { - promotedRemoteState = remotePromotionAvailability(ctx, cfg, store, catalog) + publishedRemoteState = remotePublishedOutputAvailability(ctx, cfg, store, catalog) } - writeArtifactList(out, cfg, catalog, locks, promotedRemoteState) + writeArtifactList(out, cfg, catalog, locks, publishedRemoteState) return nil } @@ -532,7 +532,7 @@ func LocksAdd(ctx context.Context, args []string, out io.Writer) error { if err != nil { return fmt.Errorf("locks add: %w", err) } - if _, err := config.ValidateArchiveLockRules([]config.ArchiveLockRule{{Source: source}}, cfg.Pipeline.Scriptorium, "locks add"); err != nil { + if _, err := config.ValidatePublishLockRules([]config.PublishLockRule{{Source: source}}, cfg.Pipeline.Scriptorium, "locks add"); err != nil { return fmt.Errorf("locks add: %w", err) } if _, ok := lockSourceSet(locks.Static)[source]; ok { @@ -542,12 +542,12 @@ func LocksAdd(ctx context.Context, args []string, out io.Writer) error { if _, exists := remoteSet[source]; exists && !force { return fmt.Errorf("locks add: remote lock for %q already exists; pass --force to update", source) } - remoteSet[source] = config.ArchiveLockRule{Source: source, Reason: strings.TrimSpace(reason)} + remoteSet[source] = config.PublishLockRule{Source: source, Reason: strings.TrimSpace(reason)} remoteLocks := lockMapValues(remoteSet) - if _, err := config.ValidateArchiveLockRules(remoteLocks, cfg.Pipeline.Scriptorium, "locks"); err != nil { + if _, err := config.ValidatePublishLockRules(remoteLocks, cfg.Pipeline.Scriptorium, "locks"); err != nil { return fmt.Errorf("locks add: %w", err) } - if err := uploadRemoteLockStore(ctx, store, locks.Key, &config.ArchiveLockStore{Locks: remoteLocks}); err != nil { + if err := uploadRemoteLockStore(ctx, store, locks.Key, &config.PublishLockStore{Locks: remoteLocks}); err != nil { return fmt.Errorf("locks add: %w", err) } _, err = fmt.Fprintf(out, "narratio session locks add: locked %s\n", source) @@ -589,7 +589,7 @@ func LocksRemove(ctx context.Context, args []string, out io.Writer) error { if err != nil { return fmt.Errorf("locks remove: %w", err) } - if _, err := config.ValidateArchiveLockRules([]config.ArchiveLockRule{{Source: source}}, cfg.Pipeline.Scriptorium, "locks remove"); err != nil { + if _, err := config.ValidatePublishLockRules([]config.PublishLockRule{{Source: source}}, cfg.Pipeline.Scriptorium, "locks remove"); err != nil { return fmt.Errorf("locks remove: %w", err) } remoteSet := lockSourceSet(locks.Remote) @@ -601,7 +601,7 @@ func LocksRemove(ctx context.Context, args []string, out io.Writer) error { } delete(remoteSet, source) remoteLocks := lockMapValues(remoteSet) - if err := uploadRemoteLockStore(ctx, store, locks.Key, &config.ArchiveLockStore{Locks: remoteLocks}); err != nil { + if err := uploadRemoteLockStore(ctx, store, locks.Key, &config.PublishLockStore{Locks: remoteLocks}); err != nil { return fmt.Errorf("locks remove: %w", err) } _, err = fmt.Fprintf(out, "narratio session locks remove: unlocked %s\n", source) @@ -980,7 +980,7 @@ func buildHelperArtifactCatalog(cfg *config.Config) (*artifacts.ArtifactCatalog, return catalog, nil } -func writeArtifactList(out io.Writer, cfg *config.Config, catalog *artifacts.ArtifactCatalog, locks *effectiveLocks, promotedRemoteState map[string]string) { +func writeArtifactList(out io.Writer, cfg *config.Config, catalog *artifacts.ArtifactCatalog, locks *effectiveLocks, publishedRemoteState map[string]string) { lockSet := lockSourceSet(locks.All) fmt.Fprintln(out, "Built-in:") for _, id := range []string{ @@ -1000,13 +1000,13 @@ func writeArtifactList(out io.Writer, cfg *config.Config, catalog *artifacts.Art for _, req := range artifacts.CollectPreviousArtifactRequirements(configuredScriptoriumArtifacts(cfg)) { fmt.Fprintf(out, "- narratio.previous_session.artifact.%s required=%t\n", req.Name, req.Required) } - fmt.Fprintln(out, "Promoted:") - for _, rule := range cfg.Pipeline.Publish.PromoteArtifacts { - writePromotedArtifactLine(out, rule, catalog, lockSet, promotedRemoteState) + fmt.Fprintln(out, "Published:") + for _, rule := range cfg.Pipeline.Publish.Outputs { + writePublishedOutputLine(out, rule, catalog, lockSet, publishedRemoteState) } } -func writeArtifactLine(out io.Writer, source string, lockSet map[string]config.ArchiveLockRule) { +func writeArtifactLine(out io.Writer, source string, lockSet map[string]config.PublishLockRule) { parts := []string{source} if _, ok := lockSet[source]; ok { parts = append(parts, "locked") @@ -1014,13 +1014,13 @@ func writeArtifactLine(out io.Writer, source string, lockSet map[string]config.A fmt.Fprintf(out, "- %s\n", strings.Join(parts, " ")) } -func writePromotedArtifactLine(out io.Writer, rule config.ArchivePromotionRule, catalog *artifacts.ArtifactCatalog, lockSet map[string]config.ArchiveLockRule, remoteState map[string]string) { +func writePublishedOutputLine(out io.Writer, rule config.PublishOutputRule, catalog *artifacts.ArtifactCatalog, lockSet map[string]config.PublishLockRule, remoteState map[string]string) { source := strings.TrimSpace(rule.Source) parts := []string{source} if _, ok := lockSet[source]; ok { parts = append(parts, "locked") } - dest, showDest, err := helperPromotionDest(rule, catalog) + dest, showDest, err := helperPublishedOutputDest(rule, catalog) if err != nil { parts = append(parts, "remote=error") fmt.Fprintf(out, "- %s\n", strings.Join(parts, " ")) @@ -1029,35 +1029,35 @@ func writePromotedArtifactLine(out io.Writer, rule config.ArchivePromotionRule, if showDest { parts = append(parts, "dest="+dest) } - if state := remoteState[promotionRemoteStateKey(source, dest)]; state != "" { + if state := remoteState[publishedOutputRemoteStateKey(source, dest)]; state != "" { parts = append(parts, state) } fmt.Fprintf(out, "- %s\n", strings.Join(parts, " ")) } -func remotePromotionAvailability(ctx context.Context, cfg *config.Config, store storage.ObjectStore, catalog *artifacts.ArtifactCatalog) map[string]string { +func remotePublishedOutputAvailability(ctx context.Context, cfg *config.Config, store storage.ObjectStore, catalog *artifacts.ArtifactCatalog) map[string]string { out := map[string]string{} sessionPrefix := artifacts.S3SessionPrefix(cfg.Pipeline.Storage.S3.RootPrefix, cfg.Session.Campaign, cfg.Session.SessionID) - for _, rule := range cfg.Pipeline.Publish.PromoteArtifacts { + for _, rule := range cfg.Pipeline.Publish.Outputs { source := strings.TrimSpace(rule.Source) - dest, _, err := helperPromotionDest(rule, catalog) + dest, _, err := helperPublishedOutputDest(rule, catalog) if err != nil { - out[promotionRemoteStateKey(source, "")] = "remote=error" + out[publishedOutputRemoteStateKey(source, "")] = "remote=error" continue } - key := artifacts.S3PromotedArtifactKey(sessionPrefix, dest) + key := artifacts.S3PublishedOutputKey(sessionPrefix, dest) if exists, err := store.Exists(ctx, key); err == nil && exists { - out[promotionRemoteStateKey(source, dest)] = "remote=promoted" + out[publishedOutputRemoteStateKey(source, dest)] = "remote=published" } else if err != nil { - out[promotionRemoteStateKey(source, dest)] = "remote=error" + out[publishedOutputRemoteStateKey(source, dest)] = "remote=error" } else { - out[promotionRemoteStateKey(source, dest)] = "remote=missing" + out[publishedOutputRemoteStateKey(source, dest)] = "remote=missing" } } return out } -func helperPromotionDest(rule config.ArchivePromotionRule, catalog *artifacts.ArtifactCatalog) (string, bool, error) { +func helperPublishedOutputDest(rule config.PublishOutputRule, catalog *artifacts.ArtifactCatalog) (string, bool, error) { source := strings.TrimSpace(rule.Source) dest := strings.TrimSpace(rule.Dest) if dest == "" { @@ -1094,20 +1094,20 @@ func normalizeHelperArchiveRelativePath(rel string) (string, error) { return cleaned, nil } -func promotionRemoteStateKey(source, dest string) string { +func publishedOutputRemoteStateKey(source, dest string) string { return strings.TrimSpace(source) + "\x00" + strings.TrimSpace(dest) } func writeLocks(out io.Writer, cfg *config.Config, locks *effectiveLocks) { if locks == nil || len(locks.All) == 0 { - fmt.Fprintln(out, "Archive locks: none") + fmt.Fprintln(out, "Publish locks: none") return } - fmt.Fprintln(out, "Archive locks:") - promoted := map[string]config.ArchivePromotionRule{} + fmt.Fprintln(out, "Publish locks:") + published := map[string]config.PublishOutputRule{} if cfg != nil && cfg.Pipeline != nil && cfg.Pipeline.Publish != nil { - for _, rule := range cfg.Pipeline.Publish.PromoteArtifacts { - promoted[strings.TrimSpace(rule.Source)] = rule + for _, rule := range cfg.Pipeline.Publish.Outputs { + published[strings.TrimSpace(rule.Source)] = rule } } staticSet := lockSourceSet(locks.Static) @@ -1116,9 +1116,9 @@ func writeLocks(out io.Writer, cfg *config.Config, locks *effectiveLocks) { if _, ok := staticSet[lock.Source]; ok { origin = "pipeline" } - promo := "not-promoted" - if _, ok := promoted[lock.Source]; ok { - promo = "promoted" + promo := "not-published" + if _, ok := published[lock.Source]; ok { + promo = "published" } reason := strings.TrimSpace(lock.Reason) if reason == "" { @@ -1128,13 +1128,13 @@ func writeLocks(out io.Writer, cfg *config.Config, locks *effectiveLocks) { } } -func lockMapValues(in map[string]config.ArchiveLockRule) []config.ArchiveLockRule { +func lockMapValues(in map[string]config.PublishLockRule) []config.PublishLockRule { keys := make([]string, 0, len(in)) for key := range in { keys = append(keys, key) } sort.Strings(keys) - out := make([]config.ArchiveLockRule, 0, len(keys)) + out := make([]config.PublishLockRule, 0, len(keys)) for _, key := range keys { item := in[key] item.Source = key diff --git a/internal/app/operator_helpers_test.go b/internal/app/operator_helpers_test.go index ff9e880..ca546b4 100644 --- a/internal/app/operator_helpers_test.go +++ b/internal/app/operator_helpers_test.go @@ -423,7 +423,7 @@ inputs: } fake := &storage.FakeBackend{} - audioKey := artifacts.S3PromotedArtifactKey(artifacts.S3AudioPrefix(artifacts.S3SessionPrefix("dnd", "sample-campaign", "2026-05-03"), "audio/"), "alice.flac") + audioKey := artifacts.S3PublishedOutputKey(artifacts.S3AudioPrefix(artifacts.S3SessionPrefix("dnd", "sample-campaign", "2026-05-03"), "audio/"), "alice.flac") fake.SeedObject(storage.FakeObject{Key: audioKey, Data: []byte("audio")}) origStoreFn := newObjectStoreFromConfigFn newObjectStoreFromConfigFn = func(context.Context, *config.Config) (storage.ObjectStore, error) { @@ -501,9 +501,9 @@ func TestExecuteLocksAddListAndRemoveUseRemoteLockStore(t *testing.T) { if code != 0 { t.Fatalf("locks remove exit code = %d, want 0; stderr=%q", code, stderr.String()) } - store, err := config.LoadArchiveLockStoreBytes("locks.yml", fake.Objects[key].Data, nil) + store, err := config.LoadPublishLockStoreBytes("locks.yml", fake.Objects[key].Data, nil) if err != nil { - t.Fatalf("LoadArchiveLockStoreBytes() error = %v", err) + t.Fatalf("LoadPublishLockStoreBytes() error = %v", err) } if len(store.Locks) != 0 { t.Fatalf("locks after remove = %#v, want empty", store.Locks) @@ -693,7 +693,7 @@ func TestExecuteArtifactsListRemoteReportsPromotedAvailability(t *testing.T) { required: true `) fake := &storage.FakeBackend{} - trimmedKey := artifacts.S3PromotedArtifactKey( + trimmedKey := artifacts.S3PublishedOutputKey( artifacts.S3SessionPrefix("dnd", "sample-campaign", "2026-05-03"), "transcripts/final.trimmed.json", ) @@ -713,7 +713,7 @@ func TestExecuteArtifactsListRemoteReportsPromotedAvailability(t *testing.T) { if code != 0 { t.Fatalf("exit code = %d, want 0; stderr=%q", code, stderr.String()) } - if !strings.Contains(stdout.String(), "narratio.transcript.final_trimmed remote=promoted") { + if !strings.Contains(stdout.String(), "narratio.transcript.final_trimmed remote=published") { t.Fatalf("stdout = %q, want promoted remote availability", stdout.String()) } } @@ -732,8 +732,8 @@ func TestExecuteArtifactsListRemoteUsesPromotionDestinations(t *testing.T) { `) fake := &storage.FakeBackend{} sessionPrefix := artifacts.S3SessionPrefix("dnd", "sample-campaign", "2026-05-03") - fake.SeedObject(storage.FakeObject{Key: artifacts.S3PromotedArtifactKey(sessionPrefix, "transcripts/full.json"), Data: []byte(`{"segments":[]}`)}) - fake.SeedObject(storage.FakeObject{Key: artifacts.S3PromotedArtifactKey(sessionPrefix, "transcripts/bounds.json"), Data: []byte(`{}`)}) + fake.SeedObject(storage.FakeObject{Key: artifacts.S3PublishedOutputKey(sessionPrefix, "transcripts/full.json"), Data: []byte(`{"segments":[]}`)}) + fake.SeedObject(storage.FakeObject{Key: artifacts.S3PublishedOutputKey(sessionPrefix, "transcripts/bounds.json"), Data: []byte(`{}`)}) var storeInitCalls int restoreAppConfigTestGlobals(t, fake, &storeInitCalls, []string{sessionPath}) @@ -759,8 +759,8 @@ func TestExecuteArtifactsListRemoteUsesPromotionDestinations(t *testing.T) { } } for _, want := range []string{ - "narratio.transcript.final dest=transcripts/full.json remote=promoted", - "narratio.bounds.session dest=transcripts/bounds.json remote=promoted", + "narratio.transcript.final dest=transcripts/full.json remote=published", + "narratio.bounds.session dest=transcripts/bounds.json remote=published", } { if !strings.Contains(out, want) { t.Fatalf("stdout = %q, want %q", out, want) @@ -783,8 +783,8 @@ func TestExecuteStatusReportsRemoteArtifactCatalog(t *testing.T) { fake := &storage.FakeBackend{} sessionPrefix := artifacts.S3SessionPrefix("dnd", "sample-campaign", "2026-05-03") manifestKey, runIDKey := artifacts.ResolveArchiveCurrentStateKeys(sessionPrefix) - trimmedKey := artifacts.S3PromotedArtifactKey(sessionPrefix, "transcripts/final.trimmed.json") - fullKey := artifacts.S3PromotedArtifactKey(sessionPrefix, "transcripts/full.json") + trimmedKey := artifacts.S3PublishedOutputKey(sessionPrefix, "transcripts/final.trimmed.json") + fullKey := artifacts.S3PublishedOutputKey(sessionPrefix, "transcripts/full.json") lockKey := artifacts.S3SessionLocksKey(sessionPrefix) fake.SeedObject(storage.FakeObject{Key: runIDKey, Data: []byte("20260519T010203Z-a1b2c3d4\n")}) fake.SeedObject(storage.FakeObject{Key: manifestKey, Data: restoreManifestJSON(t, "2026-05-03", "sample-campaign")}) @@ -811,10 +811,10 @@ func TestExecuteStatusReportsRemoteArtifactCatalog(t *testing.T) { "Built-in:", "Configured:", "Previous-session:", - "Promoted:", + "Published:", "narratio.transcript.final_trimmed locked", - "narratio.transcript.final_trimmed locked remote=promoted", - "narratio.transcript.final dest=transcripts/full.json remote=promoted", + "narratio.transcript.final_trimmed locked remote=published", + "narratio.transcript.final dest=transcripts/full.json remote=published", } { if !strings.Contains(out, want) { t.Fatalf("stdout = %q, want %q", out, want) @@ -850,13 +850,13 @@ func TestExecuteStatusReportsRemoteArtifactCatalogErrorsWithoutFailing(t *testin t.Fatalf("exit code = %d, want 0; stderr=%q", code, stderr.String()) } out := stdout.String() - if !strings.Contains(out, "Remote archive: missing or unavailable:") { + if !strings.Contains(out, "Remote publish: missing or unavailable:") { t.Fatalf("stdout = %q, want remote archive unavailable state", out) } if !strings.Contains(out, "Remote outputs:") || !strings.Contains(out, "narratio.transcript.final_trimmed remote=error") { t.Fatalf("stdout = %q, want remote output error state", out) } - if !strings.Contains(out, "Archive locks: error:") { + if !strings.Contains(out, "Publish locks: error:") { t.Fatalf("stdout = %q, want archive locks error", out) } } @@ -883,7 +883,7 @@ func TestExecuteArchiveLoadsRemoteLocks(t *testing.T) { if code != 0 { t.Fatalf("exit code = %d, want 0; stderr=%q", code, stderr.String()) } - promotedKey := artifacts.S3PromotedArtifactKey(artifacts.S3SessionPrefix("dnd", "sample-campaign", "2026-05-03"), "transcripts/final.trimmed.json") + promotedKey := artifacts.S3PublishedOutputKey(artifacts.S3SessionPrefix("dnd", "sample-campaign", "2026-05-03"), "transcripts/final.trimmed.json") if _, ok := fake.Objects[promotedKey]; ok { t.Fatalf("locked promoted key %q was uploaded", promotedKey) } diff --git a/internal/app/post_archive_cleanup_test.go b/internal/app/post_archive_cleanup_test.go index 208655a..adb310c 100644 --- a/internal/app/post_archive_cleanup_test.go +++ b/internal/app/post_archive_cleanup_test.go @@ -196,7 +196,7 @@ func TestPostArchiveCleanupNotRunWhenPromotionIsMissing(t *testing.T) { cfg, seed, runID := archiveStageCleanupFixture(t) cfg.Pipeline.Spool.DeleteAudioAfterPublish = true cfg.Pipeline.Workspace.CleanupAfterPublish = true - cfg.Pipeline.Publish.PromoteArtifacts = []config.ArchivePromotionRule{ + cfg.Pipeline.Publish.Outputs = []config.PublishOutputRule{ {Source: "narratio.transcript.base", Dest: "transcripts/base.json", Required: boolPtr(true)}, } @@ -205,8 +205,8 @@ func TestPostArchiveCleanupNotRunWhenPromotionIsMissing(t *testing.T) { t.Fatalf("Select(publish) error = %v", err) } _, err = executeStages(context.Background(), cfg, []stage.Stage{archiveStageImpl}, RunOptions{Env: &Env{ObjectStore: &storage.FakeBackend{}}}) - if err == nil || !strings.Contains(err.Error(), "required promotion source unavailable") { - t.Fatalf("executeStages() error = %v, want promotion-missing failure", err) + if err == nil || !strings.Contains(err.Error(), "required output source unavailable") { + t.Fatalf("executeStages() error = %v, want required output source unavailable failure", err) } assertExists(t, seed.spoolAudioDir) @@ -270,7 +270,7 @@ func cleanupFixtureConfig(t *testing.T) (*config.Config, cleanupSeed) { t.Helper() cfg := testConfig(t) - cfg.Pipeline.Publish = &config.ArchiveConfig{Enabled: boolPtr(true), UploadRun: boolPtr(true)} + cfg.Pipeline.Publish = &config.PublishConfig{Enabled: boolPtr(true), UploadRun: boolPtr(true)} cfg.Pipeline.Spool.Root = filepath.Join(t.TempDir(), "spool") runID := "20260516T010203Z-1a2b3c4d" @@ -329,10 +329,10 @@ func archiveStageCleanupFixture(t *testing.T) (*config.Config, cleanupSeed, stri Bucket: "my-dnd-archive", RootPrefix: "dnd", } - cfg.Pipeline.Publish = &config.ArchiveConfig{ + cfg.Pipeline.Publish = &config.PublishConfig{ Enabled: boolPtr(true), UploadRun: boolPtr(true), - PromoteArtifacts: []config.ArchivePromotionRule{ + Outputs: []config.PublishOutputRule{ {Source: "narratio.transcript.final_trimmed", Dest: "transcripts/final.trimmed.json", Required: boolPtr(true)}, {Source: "narratio.artifact.session_recap", Dest: "artifacts/session_recap.md", Required: boolPtr(true)}, }, diff --git a/internal/app/remote_locks.go b/internal/app/remote_locks.go index 6e5ca9c..a94a809 100644 --- a/internal/app/remote_locks.go +++ b/internal/app/remote_locks.go @@ -13,9 +13,9 @@ import ( ) type effectiveLocks struct { - Static []config.ArchiveLockRule - Remote []config.ArchiveLockRule - All []config.ArchiveLockRule + Static []config.PublishLockRule + Remote []config.PublishLockRule + All []config.PublishLockRule Key string } @@ -34,7 +34,7 @@ func remoteLocksKey(cfg *config.Config) (string, error) { return artifacts.S3SessionLocksKey(sessionPrefix), nil } -func loadRemoteLockStore(ctx context.Context, cfg *config.Config, store storage.ObjectStore) (*config.ArchiveLockStore, string, error) { +func loadRemoteLockStore(ctx context.Context, cfg *config.Config, store storage.ObjectStore) (*config.PublishLockStore, string, error) { key, err := remoteLocksKey(cfg) if err != nil { return nil, "", err @@ -44,7 +44,7 @@ func loadRemoteLockStore(ctx context.Context, cfg *config.Config, store storage. return nil, key, fmt.Errorf("check remote locks %q: %w", key, err) } if !exists { - return &config.ArchiveLockStore{}, key, nil + return &config.PublishLockStore{}, key, nil } tmp, err := downloadObjectToTemp(ctx, store, key, "narratio-locks-*.yml") if err != nil { @@ -55,7 +55,7 @@ func loadRemoteLockStore(ctx context.Context, cfg *config.Config, store storage. if err != nil { return nil, key, fmt.Errorf("read remote locks %q: %w", key, err) } - lockStore, err := config.LoadArchiveLockStoreBytes("s3://"+s3BucketName(cfg.Pipeline)+"/"+key, data, cfg.Pipeline.Scriptorium) + lockStore, err := config.LoadPublishLockStoreBytes("s3://"+s3BucketName(cfg.Pipeline)+"/"+key, data, cfg.Pipeline.Scriptorium) if err != nil { return nil, key, err } @@ -67,41 +67,41 @@ func loadEffectiveLocks(ctx context.Context, cfg *config.Config, store storage.O if store == nil { return &effectiveLocks{ Static: staticLocks, - All: append([]config.ArchiveLockRule(nil), staticLocks...), + All: append([]config.PublishLockRule(nil), staticLocks...), }, nil } lockStore, key, err := loadRemoteLockStore(ctx, cfg, store) if err != nil { return nil, err } - remoteLocks := append([]config.ArchiveLockRule(nil), lockStore.Locks...) + remoteLocks := append([]config.PublishLockRule(nil), lockStore.Locks...) return &effectiveLocks{ Static: staticLocks, Remote: remoteLocks, - All: config.MergeArchiveLockRules(staticLocks, remoteLocks), + All: config.MergePublishLockRules(staticLocks, remoteLocks), Key: key, }, nil } -func staticArchiveLocks(cfg *config.Config) []config.ArchiveLockRule { +func staticArchiveLocks(cfg *config.Config) []config.PublishLockRule { if cfg == nil || cfg.Pipeline == nil || cfg.Pipeline.Publish == nil { return nil } - return append([]config.ArchiveLockRule(nil), cfg.Pipeline.Publish.Locks...) + return append([]config.PublishLockRule(nil), cfg.Pipeline.Publish.Locks...) } -func applyEffectiveLocks(cfg *config.Config, locks []config.ArchiveLockRule) { +func applyEffectiveLocks(cfg *config.Config, locks []config.PublishLockRule) { if cfg == nil || cfg.Pipeline == nil { return } if cfg.Pipeline.Publish == nil { - cfg.Pipeline.Publish = &config.ArchiveConfig{} + cfg.Pipeline.Publish = &config.PublishConfig{} } - cfg.Pipeline.Publish.Locks = append([]config.ArchiveLockRule(nil), locks...) + cfg.Pipeline.Publish.Locks = append([]config.PublishLockRule(nil), locks...) } -func uploadRemoteLockStore(ctx context.Context, store storage.ObjectStore, key string, lockStore *config.ArchiveLockStore) error { - data, err := config.MarshalArchiveLockStore(lockStore) +func uploadRemoteLockStore(ctx context.Context, store storage.ObjectStore, key string, lockStore *config.PublishLockStore) error { + data, err := config.MarshalPublishLockStore(lockStore) if err != nil { return err } @@ -124,8 +124,8 @@ func uploadRemoteLockStore(ctx context.Context, store storage.ObjectStore, key s return nil } -func lockSourceSet(locks []config.ArchiveLockRule) map[string]config.ArchiveLockRule { - out := make(map[string]config.ArchiveLockRule, len(locks)) +func lockSourceSet(locks []config.PublishLockRule) map[string]config.PublishLockRule { + out := make(map[string]config.PublishLockRule, len(locks)) for _, lock := range locks { source := strings.TrimSpace(lock.Source) if source == "" { diff --git a/internal/app/runner_test.go b/internal/app/runner_test.go index 515f705..4141276 100644 --- a/internal/app/runner_test.go +++ b/internal/app/runner_test.go @@ -254,10 +254,10 @@ func TestExecuteStagesArchiveSkipsRequiredUnselectedConfiguredPromotion(t *testi Bucket: "my-dnd-archive", RootPrefix: "dnd", } - cfg.Pipeline.Publish = &config.ArchiveConfig{ + cfg.Pipeline.Publish = &config.PublishConfig{ Enabled: boolPtr(true), UploadRun: boolPtr(true), - PromoteArtifacts: []config.ArchivePromotionRule{ + Outputs: []config.PublishOutputRule{ {Source: "narratio.artifact.session_recap", Dest: "artifacts/session_recap.md", Required: boolPtr(true)}, }, } @@ -312,9 +312,9 @@ func TestExecuteStagesArchiveSkipsRequiredUnselectedConfiguredPromotion(t *testi t.Fatalf("Load manifest error = %v", err) } meta := loadedManifest.Stages["publish"].Metadata - skipped, ok := meta["skipped_unselected_promotions"].([]any) + skipped, ok := meta["skipped_unselected_outputs"].([]any) if !ok || len(skipped) != 1 { - t.Fatalf("skipped_unselected_promotions = %#v, want one item", meta["skipped_unselected_promotions"]) + t.Fatalf("skipped_unselected_outputs = %#v, want one item", meta["skipped_unselected_outputs"]) } item, ok := skipped[0].(map[string]any) if !ok { @@ -950,7 +950,7 @@ func TestAdapterBackedStageFailureMarksManifestFailed(t *testing.T) { } } if tc.name == "publish" { - cfg.Pipeline.Publish = &config.ArchiveConfig{ + cfg.Pipeline.Publish = &config.PublishConfig{ Enabled: boolPtr(true), UploadRun: boolPtr(true), } diff --git a/internal/app/session_oriented_cli_test.go b/internal/app/session_oriented_cli_test.go index 1d11627..1313f08 100644 --- a/internal/app/session_oriented_cli_test.go +++ b/internal/app/session_oriented_cli_test.go @@ -246,7 +246,7 @@ func TestExecuteSessionSubcommandsAcceptPositionalSessionID(t *testing.T) { { name: "locks", args: []string{"session", "locks", "2026-05-03", "--config", pipelinePath, "--campaign-file", campaignPath, "--session", sessionPath}, - want: "Archive locks:", + want: "Publish locks:", }, } @@ -325,9 +325,9 @@ func TestExecuteSessionLocksMutationAcceptsPositionalSessionID(t *testing.T) { if code != 0 { t.Fatalf("locks remove exit code = %d, want 0; stderr=%q", code, stderr.String()) } - store, err := config.LoadArchiveLockStoreBytes("locks.yml", fake.Objects[key].Data, nil) + store, err := config.LoadPublishLockStoreBytes("locks.yml", fake.Objects[key].Data, nil) if err != nil { - t.Fatalf("LoadArchiveLockStoreBytes() error = %v", err) + t.Fatalf("LoadPublishLockStoreBytes() error = %v", err) } if len(store.Locks) != 0 { t.Fatalf("locks after remove = %#v, want empty", store.Locks) diff --git a/internal/artifacts/s3_keys.go b/internal/artifacts/s3_keys.go index 1913e92..4af5733 100644 --- a/internal/artifacts/s3_keys.go +++ b/internal/artifacts/s3_keys.go @@ -58,9 +58,9 @@ func S3CurrentRunPointerKey(sessionPrefix string) string { return path.Join(strings.TrimSuffix(cleanS3Key(sessionPrefix), "/"), config.S3CurrentSegment, config.S3RunIDFile) } -// S3PromotedArtifactKey returns the destination key for one promoted artifact. -// Format: {session_prefix}/{promotion.to} -func S3PromotedArtifactKey(sessionPrefix, to string) string { +// S3PublishedOutputKey returns the destination key for one published output. +// Format: {session_prefix}/{output.dest} +func S3PublishedOutputKey(sessionPrefix, to string) string { return path.Join(strings.TrimSuffix(cleanS3Key(sessionPrefix), "/"), cleanS3Key(to)) } diff --git a/internal/artifacts/s3_keys_test.go b/internal/artifacts/s3_keys_test.go index e5874c4..25fa8b3 100644 --- a/internal/artifacts/s3_keys_test.go +++ b/internal/artifacts/s3_keys_test.go @@ -43,7 +43,7 @@ func TestS3KeyConstruction(t *testing.T) { t.Fatalf("manifest key = %q", manifestKey) } - promoted := S3PromotedArtifactKey(sessionPrefix, "transcripts/final.trimmed.json") + promoted := S3PublishedOutputKey(sessionPrefix, "transcripts/final.trimmed.json") if promoted != "dnd/campaigns/forsaken/sessions/2026-04-19/transcripts/final.trimmed.json" { t.Fatalf("promoted key = %q", promoted) } diff --git a/internal/config/config.go b/internal/config/config.go index ac3803d..9e92b0d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -21,7 +21,7 @@ type PipelineConfig struct { Storage StorageConfig `yaml:"storage"` Spool SpoolConfig `yaml:"spool"` Cache CacheConfig `yaml:"cache"` - Publish *ArchiveConfig `yaml:"publish"` + Publish *PublishConfig `yaml:"publish"` Secrets *SecretsConfig `yaml:"secrets"` WhisperX WhisperXConfig `yaml:"whisperx"` Seriatim SeriatimConfig `yaml:"seriatim"` @@ -102,31 +102,31 @@ type CacheConfig struct { S3Audio *bool `yaml:"s3_audio"` } -// ArchiveConfig configures archive behavior and artifact promotions. -type ArchiveConfig struct { - Enabled *bool `yaml:"enabled"` - UploadRun *bool `yaml:"upload_run"` - PromoteArtifacts []ArchivePromotionRule `yaml:"outputs"` - Locks []ArchiveLockRule `yaml:"locks"` +// PublishConfig configures publish behavior and source-based output uploads. +type PublishConfig struct { + Enabled *bool `yaml:"enabled"` + UploadRun *bool `yaml:"upload_run"` + Outputs []PublishOutputRule `yaml:"outputs"` + Locks []PublishLockRule `yaml:"locks"` } -// ArchivePromotionRule configures one artifact promotion mapping. -type ArchivePromotionRule struct { +// PublishOutputRule configures one source-to-destination publish mapping. +type PublishOutputRule struct { Source string `yaml:"source"` Dest string `yaml:"dest"` Required *bool `yaml:"required"` } -// ArchiveLockRule prevents one source-based promotion from overwriting its -// top-level archive destination. -type ArchiveLockRule struct { +// PublishLockRule prevents one source from overwriting its top-level published +// destination. +type PublishLockRule struct { Source string `yaml:"source"` Reason string `yaml:"reason"` } -// ArchiveLockStore is the mutable per-session remote lock store. -type ArchiveLockStore struct { - Locks []ArchiveLockRule `yaml:"locks"` +// PublishLockStore is the mutable per-session remote lock store. +type PublishLockStore struct { + Locks []PublishLockRule `yaml:"locks"` } // WhisperXConfig configures WhisperX adapter settings. diff --git a/internal/config/defaults.go b/internal/config/defaults.go index ac96972..2d61115 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -76,9 +76,9 @@ const ( S3RunIDFile = "run_id.txt" ) -// DefaultArchivePromoteArtifacts defines the default archive promotion rules. +// DefaultPublishOutputs defines the default publish output rules. // Callers should copy this slice before mutating. -var DefaultArchivePromoteArtifacts = []ArchivePromotionRule{ +var DefaultPublishOutputs = []PublishOutputRule{ {Source: artifactmodel.SourceTranscriptFinalTrimmed, Dest: PathTranscriptFinalTrimmed}, } diff --git a/internal/config/load.go b/internal/config/load.go index 568c51a..78ffc7b 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -84,29 +84,29 @@ func LoadSessionBytesWithOptions(label string, data []byte, opts SessionLoadOpti return &cfg, nil } -// LoadArchiveLockStoreBytes loads a mutable session lock store with strict +// LoadPublishLockStoreBytes loads a mutable session lock store with strict // field checking and source validation. -func LoadArchiveLockStoreBytes(label string, data []byte, scriptorium *ScriptoriumConfig) (*ArchiveLockStore, error) { - var store ArchiveLockStore - if err := decodeStrictYAMLFromReader("archive lock store", label, strings.NewReader(string(data)), &store); err != nil { - return nil, fmt.Errorf("load archive lock store: %w", err) +func LoadPublishLockStoreBytes(label string, data []byte, scriptorium *ScriptoriumConfig) (*PublishLockStore, error) { + var store PublishLockStore + if err := decodeStrictYAMLFromReader("publish lock store", label, strings.NewReader(string(data)), &store); err != nil { + return nil, fmt.Errorf("load publish lock store: %w", err) } - locks, err := ValidateArchiveLockRules(store.Locks, scriptorium, "locks") + locks, err := ValidatePublishLockRules(store.Locks, scriptorium, "locks") if err != nil { - return nil, fmt.Errorf("load archive lock store: %w", err) + return nil, fmt.Errorf("load publish lock store: %w", err) } store.Locks = locks return &store, nil } -// MarshalArchiveLockStore serializes a mutable lock store as strict-compatible YAML. -func MarshalArchiveLockStore(store *ArchiveLockStore) ([]byte, error) { +// MarshalPublishLockStore serializes a mutable lock store as strict-compatible YAML. +func MarshalPublishLockStore(store *PublishLockStore) ([]byte, error) { if store == nil { - store = &ArchiveLockStore{} + store = &PublishLockStore{} } data, err := yaml.Marshal(store) if err != nil { - return nil, fmt.Errorf("marshal archive lock store: %w", err) + return nil, fmt.Errorf("marshal publish lock store: %w", err) } return data, nil } @@ -328,7 +328,7 @@ func applyPipelineDefaults(cfg *PipelineConfig) { applyStorageDefaults(&cfg.Storage) applySpoolDefaults(&cfg.Spool) applyCacheDefaults(&cfg.Cache) - applyArchiveDefaults(&cfg.Publish) + applyPublishDefaults(&cfg.Publish) applyWhisperXDefaults(&cfg.WhisperX) applySeriatimDefaults(&cfg.Seriatim) applyAuditaDefaults(&cfg.Audita) @@ -397,12 +397,12 @@ func applyCacheDefaults(cfg *CacheConfig) { } } -func applyArchiveDefaults(cfg **ArchiveConfig) { +func applyPublishDefaults(cfg **PublishConfig) { if cfg == nil { return } if *cfg == nil { - *cfg = &ArchiveConfig{} + *cfg = &PublishConfig{} } if (*cfg).Enabled == nil { @@ -411,12 +411,12 @@ func applyArchiveDefaults(cfg **ArchiveConfig) { if (*cfg).UploadRun == nil { (*cfg).UploadRun = boolPtr(DefaultArchiveUploadRun) } - if len((*cfg).PromoteArtifacts) == 0 { - (*cfg).PromoteArtifacts = append([]ArchivePromotionRule(nil), DefaultArchivePromoteArtifacts...) + if len((*cfg).Outputs) == 0 { + (*cfg).Outputs = append([]PublishOutputRule(nil), DefaultPublishOutputs...) } - for i := range (*cfg).PromoteArtifacts { - if (*cfg).PromoteArtifacts[i].Required == nil { - (*cfg).PromoteArtifacts[i].Required = boolPtr(true) + for i := range (*cfg).Outputs { + if (*cfg).Outputs[i].Required == nil { + (*cfg).Outputs[i].Required = boolPtr(true) } } } diff --git a/internal/config/storage_archive_test.go b/internal/config/storage_archive_test.go index d6837d0..6526052 100644 --- a/internal/config/storage_archive_test.go +++ b/internal/config/storage_archive_test.go @@ -179,10 +179,10 @@ func TestSpoolAndArchiveDefaults(t *testing.T) { if cfg.Pipeline.Publish.UploadRun == nil || !*cfg.Pipeline.Publish.UploadRun { t.Fatalf("archive.upload_run = %#v, want true", cfg.Pipeline.Publish.UploadRun) } - if len(cfg.Pipeline.Publish.PromoteArtifacts) != 1 { - t.Fatalf("publish.outputs len = %d, want 1 default", len(cfg.Pipeline.Publish.PromoteArtifacts)) + if len(cfg.Pipeline.Publish.Outputs) != 1 { + t.Fatalf("publish.outputs len = %d, want 1 default", len(cfg.Pipeline.Publish.Outputs)) } - item := cfg.Pipeline.Publish.PromoteArtifacts[0] + item := cfg.Pipeline.Publish.Outputs[0] if item.Required == nil || !*item.Required { t.Fatalf("publish.outputs[0].required = %#v, want true", item.Required) } @@ -236,7 +236,7 @@ func TestArchivePromotionValidation(t *testing.T) { - source: "narratio.transcript.final" dest: "artifacts/shared.md" `, - wantErr: "duplicates another archive promotion destination", + wantErr: "duplicates another publish output destination", }, { name: "configured source requires configured artifact key", @@ -349,11 +349,11 @@ publish: if err := Validate(cfg); err != nil { t.Fatalf("Validate() error = %v", err) } - if len(cfg.Pipeline.Publish.PromoteArtifacts) != 1 { - t.Fatalf("publish.outputs len = %d, want 1", len(cfg.Pipeline.Publish.PromoteArtifacts)) + if len(cfg.Pipeline.Publish.Outputs) != 1 { + t.Fatalf("publish.outputs len = %d, want 1", len(cfg.Pipeline.Publish.Outputs)) } - if cfg.Pipeline.Publish.PromoteArtifacts[0].Dest != tt.wantDest { - t.Fatalf("publish.outputs[0].dest = %q, want %q", cfg.Pipeline.Publish.PromoteArtifacts[0].Dest, tt.wantDest) + if cfg.Pipeline.Publish.Outputs[0].Dest != tt.wantDest { + t.Fatalf("publish.outputs[0].dest = %q, want %q", cfg.Pipeline.Publish.Outputs[0].Dest, tt.wantDest) } }) } @@ -414,7 +414,7 @@ publish: - source: narratio.transcript.final_trimmed - source: " narratio.transcript.final_trimmed " `, - wantErr: "duplicates another archive lock source", + wantErr: "duplicates another publish lock source", }, } @@ -496,18 +496,18 @@ publish: } func TestArchiveLockStoreBytesStrictDecodeAndValidation(t *testing.T) { - store, err := LoadArchiveLockStoreBytes("locks.yml", []byte(`locks: + store, err := LoadPublishLockStoreBytes("locks.yml", []byte(`locks: - source: narratio.transcript.final_trimmed reason: reviewed `), nil) if err != nil { - t.Fatalf("LoadArchiveLockStoreBytes() error = %v", err) + t.Fatalf("LoadPublishLockStoreBytes() error = %v", err) } if len(store.Locks) != 1 || store.Locks[0].Source != "narratio.transcript.final_trimmed" || store.Locks[0].Reason != "reviewed" { t.Fatalf("locks = %#v", store.Locks) } - _, err = LoadArchiveLockStoreBytes("locks.yml", []byte(`locks: + _, err = LoadPublishLockStoreBytes("locks.yml", []byte(`locks: - source: narratio.transcript.final_trimmed dest: transcripts/final.trimmed.json `), nil) @@ -515,19 +515,19 @@ func TestArchiveLockStoreBytesStrictDecodeAndValidation(t *testing.T) { t.Fatalf("unknown field error = %v, want strict decode failed", err) } - _, err = LoadArchiveLockStoreBytes("locks.yml", []byte(`locks: + _, err = LoadPublishLockStoreBytes("locks.yml", []byte(`locks: - source: narratio.transcript.final_trimmed - source: narratio.transcript.final_trimmed `), nil) - if err == nil || !strings.Contains(err.Error(), "duplicates another archive lock source") { + if err == nil || !strings.Contains(err.Error(), "duplicates another publish lock source") { t.Fatalf("duplicate error = %v", err) } } func TestMergeArchiveLockRulesStaticWins(t *testing.T) { - merged := MergeArchiveLockRules( - []ArchiveLockRule{{Source: "narratio.transcript.final_trimmed", Reason: "static"}}, - []ArchiveLockRule{ + merged := MergePublishLockRules( + []PublishLockRule{{Source: "narratio.transcript.final_trimmed", Reason: "static"}}, + []PublishLockRule{ {Source: "narratio.transcript.final_trimmed", Reason: "remote"}, {Source: "narratio.transcript.final", Reason: "remote full"}, }, diff --git a/internal/config/validate.go b/internal/config/validate.go index f172e91..2066cbb 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -68,7 +68,7 @@ func validatePipeline(cfg *PipelineConfig) error { if err := validateCache(cfg.Cache); err != nil { return err } - if err := validateArchive(cfg.Publish, cfg.Scriptorium); err != nil { + if err := validatePublish(cfg.Publish, cfg.Scriptorium); err != nil { return err } if err := validateWhisperX(cfg.WhisperX); err != nil { @@ -129,39 +129,39 @@ func validateCache(cfg CacheConfig) error { return nil } -func validateArchive(cfg *ArchiveConfig, scriptorium *ScriptoriumConfig) error { +func validatePublish(cfg *PublishConfig, scriptorium *ScriptoriumConfig) error { if cfg == nil { return nil } seenDest := map[string]struct{}{} - for i, item := range cfg.PromoteArtifacts { + for i, item := range cfg.Outputs { prefix := fmt.Sprintf("pipeline.publish.outputs[%d]", i) source := strings.TrimSpace(item.Source) if source == "" { return fmt.Errorf("%s.source is required", prefix) } - if _, err := archiveSourceKnown(source, scriptorium); err != nil { + if _, err := publishSourceKnown(source, scriptorium); err != nil { return fmt.Errorf("%s.source %q is unsupported: %w", prefix, item.Source, err) } dest := strings.TrimSpace(item.Dest) if dest == "" { - derivedDest, err := deriveArchivePromotionDest(source, scriptorium) + derivedDest, err := derivePublishOutputDest(source, scriptorium) if err != nil { return fmt.Errorf("%s.dest is required when destination cannot be derived from %q: %w", prefix, source, err) } dest = derivedDest - cfg.PromoteArtifacts[i].Dest = derivedDest + cfg.Outputs[i].Dest = derivedDest } if err := validateRelativeSafePath(prefix+".dest", dest); err != nil { return err } normalizedDest := filepath.ToSlash(filepath.Clean(dest)) if _, ok := seenDest[normalizedDest]; ok { - return fmt.Errorf("%s.dest %q duplicates another archive promotion destination", prefix, dest) + return fmt.Errorf("%s.dest %q duplicates another publish output destination", prefix, dest) } seenDest[normalizedDest] = struct{}{} } - locks, err := ValidateArchiveLockRules(cfg.Locks, scriptorium, "pipeline.publish.locks") + locks, err := ValidatePublishLockRules(cfg.Locks, scriptorium, "pipeline.publish.locks") if err != nil { return err } @@ -169,10 +169,10 @@ func validateArchive(cfg *ArchiveConfig, scriptorium *ScriptoriumConfig) error { return nil } -// ValidateArchiveLockRules validates and normalizes source-based archive locks. -func ValidateArchiveLockRules(locks []ArchiveLockRule, scriptorium *ScriptoriumConfig, label string) ([]ArchiveLockRule, error) { +// ValidatePublishLockRules validates and normalizes source-based publish locks. +func ValidatePublishLockRules(locks []PublishLockRule, scriptorium *ScriptoriumConfig, label string) ([]PublishLockRule, error) { seenLocks := map[string]struct{}{} - out := make([]ArchiveLockRule, 0, len(locks)) + out := make([]PublishLockRule, 0, len(locks)) if strings.TrimSpace(label) == "" { label = "publish.locks" } @@ -182,14 +182,14 @@ func ValidateArchiveLockRules(locks []ArchiveLockRule, scriptorium *ScriptoriumC if source == "" { return nil, fmt.Errorf("%s.source is required", prefix) } - if _, err := archiveSourceKnown(source, scriptorium); err != nil { + if _, err := publishSourceKnown(source, scriptorium); err != nil { return nil, fmt.Errorf("%s.source %q is unsupported: %w", prefix, item.Source, err) } if _, ok := seenLocks[source]; ok { - return nil, fmt.Errorf("%s.source %q duplicates another archive lock source", prefix, source) + return nil, fmt.Errorf("%s.source %q duplicates another publish lock source", prefix, source) } seenLocks[source] = struct{}{} - out = append(out, ArchiveLockRule{ + out = append(out, PublishLockRule{ Source: source, Reason: strings.TrimSpace(item.Reason), }) @@ -197,17 +197,17 @@ func ValidateArchiveLockRules(locks []ArchiveLockRule, scriptorium *ScriptoriumC return out, nil } -// MergeArchiveLockRules returns the union of static and remote locks. Static +// MergePublishLockRules returns the union of static and remote locks. Static // locks win when both sources contain the same lock. -func MergeArchiveLockRules(staticLocks, remoteLocks []ArchiveLockRule) []ArchiveLockRule { - out := make([]ArchiveLockRule, 0, len(staticLocks)+len(remoteLocks)) +func MergePublishLockRules(staticLocks, remoteLocks []PublishLockRule) []PublishLockRule { + out := make([]PublishLockRule, 0, len(staticLocks)+len(remoteLocks)) seen := map[string]struct{}{} for _, item := range staticLocks { source := strings.TrimSpace(item.Source) if source == "" { continue } - out = append(out, ArchiveLockRule{Source: source, Reason: strings.TrimSpace(item.Reason)}) + out = append(out, PublishLockRule{Source: source, Reason: strings.TrimSpace(item.Reason)}) seen[source] = struct{}{} } for _, item := range remoteLocks { @@ -218,13 +218,13 @@ func MergeArchiveLockRules(staticLocks, remoteLocks []ArchiveLockRule) []Archive if _, ok := seen[source]; ok { continue } - out = append(out, ArchiveLockRule{Source: source, Reason: strings.TrimSpace(item.Reason)}) + out = append(out, PublishLockRule{Source: source, Reason: strings.TrimSpace(item.Reason)}) seen[source] = struct{}{} } return out } -func archiveSourceKnown(source string, scriptorium *ScriptoriumConfig) (string, error) { +func publishSourceKnown(source string, scriptorium *ScriptoriumConfig) (string, error) { trimmed := strings.TrimSpace(source) if _, ok := artifactmodel.LookupRuntimeTranscriptArtifact(trimmed); ok { return "", nil @@ -247,7 +247,7 @@ func archiveSourceKnown(source string, scriptorium *ScriptoriumConfig) (string, return artifactKey, nil } -func deriveArchivePromotionDest(source string, scriptorium *ScriptoriumConfig) (string, error) { +func derivePublishOutputDest(source string, scriptorium *ScriptoriumConfig) (string, error) { trimmed := strings.TrimSpace(source) if spec, ok := artifactmodel.LookupRuntimeTranscriptArtifact(trimmed); ok { return spec.CanonicalRelPath, nil @@ -256,7 +256,7 @@ func deriveArchivePromotionDest(source string, scriptorium *ScriptoriumConfig) ( case "narratio.bounds.session": return filepath.ToSlash(filepath.Join(PathArtifactsDirSegment, "session_bounds.json")), nil } - artifactKey, err := archiveSourceKnown(trimmed, scriptorium) + artifactKey, err := publishSourceKnown(trimmed, scriptorium) if err != nil { return "", err } @@ -629,14 +629,14 @@ func validateCrossConfig(pipeline *PipelineConfig, session *SessionConfig) error } audioS3Enabled := session.Inputs.AudioS3 != nil - archiveUploadEnabled := archiveUploadConfiguredForS3(pipeline) - if (audioS3Enabled || archiveUploadEnabled) && strings.TrimSpace(pipeline.Storage.S3.Bucket) == "" { - return fmt.Errorf("pipeline.storage.s3.bucket is required when S3 session audio or archive upload is enabled") + publishUploadEnabled := publishUploadConfiguredForS3(pipeline) + if (audioS3Enabled || publishUploadEnabled) && strings.TrimSpace(pipeline.Storage.S3.Bucket) == "" { + return fmt.Errorf("pipeline.storage.s3.bucket is required when S3 session audio or publish upload is enabled") } return nil } -func archiveUploadConfiguredForS3(pipeline *PipelineConfig) bool { +func publishUploadConfiguredForS3(pipeline *PipelineConfig) bool { if pipeline == nil || pipeline.Publish == nil { return false } diff --git a/internal/previouscache/previouscache.go b/internal/previouscache/previouscache.go index 6d0504b..287599f 100644 --- a/internal/previouscache/previouscache.go +++ b/internal/previouscache/previouscache.go @@ -18,7 +18,7 @@ import ( const ( InputKindManifest = "previous_manifest" InputKindArtifact = "previous_artifact" - InputSource = "previous_session_archive.current" + InputSource = "previous_session_publish.current" ) type Plan struct { @@ -199,7 +199,7 @@ func BuildPlan( selectedRel := "" selectedKey := "" for _, candidate := range candidates { - remoteKey := artifacts.S3PromotedArtifactKey(previousSessionPrefix, candidate) + remoteKey := artifacts.S3PublishedOutputKey(previousSessionPrefix, candidate) exists, err := store.Exists(ctx, remoteKey) if err != nil { return nil, fmt.Errorf("check previous-session artifact object %q: %w", remoteKey, err) @@ -289,9 +289,9 @@ func artifactRelativePathCandidates( if rel, ok := manifestArtifactRelativePathBySourceID(previousManifest, sourceID); ok { appendCandidate(rel) base := path.Base(rel) - for _, promoted := range manifestPromotedPaths(previousManifest) { - if path.Base(promoted) == base { - appendCandidate(promoted) + for _, published := range manifestPublishedPaths(previousManifest) { + if path.Base(published) == base { + appendCandidate(published) } } } @@ -395,15 +395,15 @@ func manifestSessionRoot(previousManifest *manifest.Manifest) (string, bool) { return filepath.Dir(runsDir), true } -func manifestPromotedPaths(previousManifest *manifest.Manifest) []string { +func manifestPublishedPaths(previousManifest *manifest.Manifest) []string { if previousManifest == nil || len(previousManifest.Stages) == 0 { return nil } - sr := previousManifest.Stages["archive"] + sr := previousManifest.Stages["publish"] if sr == nil || sr.Metadata == nil { return nil } - raw, ok := sr.Metadata["promoted_paths"] + raw, ok := sr.Metadata["published_paths"] if !ok { return nil } diff --git a/internal/previouscache/previouscache_test.go b/internal/previouscache/previouscache_test.go index 60569c3..2b5cd4e 100644 --- a/internal/previouscache/previouscache_test.go +++ b/internal/previouscache/previouscache_test.go @@ -155,10 +155,10 @@ func previousManifestWithOutput(t *testing.T, cfg *config.Config, rel string, pr }) } if promoted != nil { - if m.Stages["archive"] == nil { - m.MarkStageSucceeded("archive", time.Date(2026, 4, 26, 10, 2, 0, 0, time.UTC), nil) + if m.Stages["publish"] == nil { + m.MarkStageSucceeded("publish", time.Date(2026, 4, 26, 10, 2, 0, 0, time.UTC), nil) } - m.Stages["archive"].Metadata = map[string]any{"promoted_paths": promoted} + m.Stages["publish"].Metadata = map[string]any{"published_paths": promoted} } return m } diff --git a/internal/stage/analyze.go b/internal/stage/analyze.go index e547377..fd65b73 100644 --- a/internal/stage/analyze.go +++ b/internal/stage/analyze.go @@ -496,13 +496,13 @@ func executeAnalyzeArtifact( if err := requireNonEmptyFile(finalOutputPath, artifactName+" output"); err != nil { return nil, fmt.Errorf("analyze: %w", err) } - promotedArtifact, err := promoteRunLocalOutput(env.ArtifactStore, finalOutputPath, canonicalOutputPath, artifacts.Ref{ + materializedArtifact, err := materializeRunLocalOutput(env.ArtifactStore, finalOutputPath, canonicalOutputPath, artifacts.Ref{ Kind: artifactName, Category: "artifacts", SessionID: sessionID, }) if err != nil { - return nil, fmt.Errorf("analyze: promote artifact output for %q: %w", artifactName, err) + return nil, fmt.Errorf("analyze: materialize artifact output for %q: %w", artifactName, err) } logPaths = append(logPaths, stdoutLogPath, stderrLogPath) @@ -529,7 +529,7 @@ func executeAnalyzeArtifact( } return &analyzeArtifactExecutionResult{ - Output: promotedArtifact, + Output: materializedArtifact, Logs: logPaths, GeneratedConfigs: generatedConfigs, Metadata: meta, diff --git a/internal/stage/analyze_test.go b/internal/stage/analyze_test.go index c2cd8f8..ceb37a7 100644 --- a/internal/stage/analyze_test.go +++ b/internal/stage/analyze_test.go @@ -277,7 +277,7 @@ func TestAnalyzeUsesRunLocalPathsAndPromotesCanonical(t *testing.T) { t.Fatalf("outputs len = %d, want 1", len(result.Outputs)) } if strings.Contains(result.Outputs[0].AbsolutePath, string(filepath.Separator)+"runs"+string(filepath.Separator)) { - t.Fatalf("promoted output path = %q, want canonical session path", result.Outputs[0].AbsolutePath) + t.Fatalf("materialized output path = %q, want canonical session path", result.Outputs[0].AbsolutePath) } } diff --git a/internal/stage/archive.go b/internal/stage/archive.go index 39afa10..1845daa 100644 --- a/internal/stage/archive.go +++ b/internal/stage/archive.go @@ -72,7 +72,7 @@ func (archiveStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S }, nil } - if err := validateArchivePrerequisites(m); err != nil { + if err := validatePublishPrerequisites(m); err != nil { return nil, fmt.Errorf("publish: %w", err) } if env.ObjectStore == nil { @@ -126,17 +126,17 @@ func (archiveStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S if err != nil { return nil, fmt.Errorf("publish: build runtime artifact catalog: %w", err) } - promotions, skippedOptional, skippedUnselected, lockedPromotions, err := resolveArchivePromotions( + publishOutputs, skippedOptionalOutputs, skippedUnselectedOutputs, lockedOutputs, err := resolvePublishOutputs( sessionPaths, m, runtimeCatalog, - env.Config.Pipeline.Publish.PromoteArtifacts, + env.Config.Pipeline.Publish.Outputs, env.Config.Pipeline.Publish.Locks, env.SelectedArtifactKeys, sessionPrefix, ) if err != nil { - return nil, fmt.Errorf("publish: resolve promotion rules: %w", err) + return nil, fmt.Errorf("publish: resolve publish output rules: %w", err) } runUploaded := make([]string, 0, len(runFiles)) for _, file := range runFiles { @@ -147,18 +147,18 @@ func (archiveStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S runUploaded = append(runUploaded, file.RelativePath) } - promotedUploaded := make([]string, 0, len(promotions)) - for _, promotion := range promotions { - key := artifacts.S3PromotedArtifactKey(sessionPrefix, promotion.Dest) + publishedUploaded := make([]string, 0, len(publishOutputs)) + for _, promotion := range publishOutputs { + key := artifacts.S3PublishedOutputKey(sessionPrefix, promotion.Dest) if _, err := env.ObjectStore.Upload(ctx, promotion.LocalPath, key, storage.UploadOptions{}); err != nil { - return nil, fmt.Errorf("publish: upload promoted output source %q to %q: %w", promotion.Source, key, err) + return nil, fmt.Errorf("publish: upload published output source %q to %q: %w", promotion.Source, key, err) } - promotedUploaded = append(promotedUploaded, promotion.Dest) + publishedUploaded = append(publishedUploaded, promotion.Dest) } previousUploaded := make([]string, 0, len(previousFiles)) for _, file := range previousFiles { - key := artifacts.S3PromotedArtifactKey(sessionPrefix, file.RelativePath) + key := artifacts.S3PublishedOutputKey(sessionPrefix, file.RelativePath) if _, err := env.ObjectStore.Upload(ctx, file.LocalPath, key, storage.UploadOptions{}); err != nil { return nil, fmt.Errorf("publish: upload previous file %q to %q: %w", file.RelativePath, key, err) } @@ -171,11 +171,11 @@ func (archiveStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S runPrefix, sessionPrefix, runUploaded, - promotedUploaded, + publishedUploaded, previousUploaded, - skippedOptional, - skippedUnselected, - lockedPromotions, + skippedOptionalOutputs, + skippedUnselectedOutputs, + lockedOutputs, currentManifestKey, )) if err != nil { @@ -203,29 +203,29 @@ func (archiveStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S return &StageResult{ Metadata: map[string]any{ - "stage": "publish", - "uploaded": true, - "s3_bucket": bucket, - "s3_run_prefix": runPrefix, - "run_files_uploaded": len(runUploaded), - "run_uploaded_paths": runUploaded, - "promoted_files_uploaded": len(promotedUploaded), - "promoted_paths": promotedUploaded, - "previous_files_uploaded": len(previousUploaded), - "previous_uploaded_paths": previousUploaded, - "skipped_optional_promotions": skippedOptional, - "skipped_unselected_promotions": skippedUnselectedPromotionMetadata(skippedUnselected), - "locked_promotion_count": len(lockedPromotions), - "locked_promotions": lockedPromotionMetadata(lockedPromotions), - "current_manifest_key": currentManifestKey, - "current_run_id_key": currentRunPointerKey, - "current_pointer_written": true, - "audio_upload_skipped": true, + "stage": "publish", + "uploaded": true, + "s3_bucket": bucket, + "s3_run_prefix": runPrefix, + "run_files_uploaded": len(runUploaded), + "run_uploaded_paths": runUploaded, + "published_files_uploaded": len(publishedUploaded), + "published_paths": publishedUploaded, + "previous_files_uploaded": len(previousUploaded), + "previous_uploaded_paths": previousUploaded, + "skipped_optional_outputs": skippedOptionalOutputs, + "skipped_unselected_outputs": skippedUnselectedOutputMetadata(skippedUnselectedOutputs), + "locked_output_count": len(lockedOutputs), + "locked_outputs": lockedOutputMetadata(lockedOutputs), + "current_manifest_key": currentManifestKey, + "current_run_id_key": currentRunPointerKey, + "current_pointer_written": true, + "audio_upload_skipped": true, }, }, nil } -type archivePromotion struct { +type publishOutput struct { Source string Dest string Required bool @@ -233,7 +233,7 @@ type archivePromotion struct { Provenance string } -type archiveLockedPromotion struct { +type publishLockedOutput struct { Source string Dest string RemoteKey string @@ -243,7 +243,7 @@ type archiveLockedPromotion struct { Provenance string } -type archiveSkippedUnselectedPromotion struct { +type publishSkippedUnselectedOutput struct { Source string Dest string Required bool @@ -265,7 +265,7 @@ func archiveRunUploadDisabled(env *Env) bool { return cfg.UploadRun != nil && !*cfg.UploadRun } -func validateArchivePrerequisites(m *manifest.Manifest) error { +func validatePublishPrerequisites(m *manifest.Manifest) error { if m == nil { return fmt.Errorf("manifest is required") } @@ -343,32 +343,32 @@ func archiveSessionPaths(env *Env, m *manifest.Manifest) artifacts.SessionPaths return store.SessionPathsFor(campaign, sessionID) } -func resolveArchivePromotions( +func resolvePublishOutputs( paths artifacts.SessionPaths, m *manifest.Manifest, catalog *artifacts.ArtifactCatalog, - rules []config.ArchivePromotionRule, - locks []config.ArchiveLockRule, + rules []config.PublishOutputRule, + locks []config.PublishLockRule, selectedArtifactKeys []string, sessionPrefix string, -) ([]archivePromotion, []string, []archiveSkippedUnselectedPromotion, []archiveLockedPromotion, error) { - out := make([]archivePromotion, 0, len(rules)) - skippedOptional := make([]string, 0) - skippedUnselected := make([]archiveSkippedUnselectedPromotion, 0) - lockedPromotions := make([]archiveLockedPromotion, 0) +) ([]publishOutput, []string, []publishSkippedUnselectedOutput, []publishLockedOutput, error) { + out := make([]publishOutput, 0, len(rules)) + skippedOptionalOutputs := make([]string, 0) + skippedUnselectedOutputs := make([]publishSkippedUnselectedOutput, 0) + lockedOutputs := make([]publishLockedOutput, 0) lockSet := archiveLockSet(locks) selectedSet := archiveSelectedArtifactSet(selectedArtifactKeys) for _, rule := range rules { source := strings.TrimSpace(rule.Source) required := rule.Required == nil || *rule.Required - dest, err := resolveArchivePromotionDest(rule, catalog) + dest, err := resolvePublishOutputDest(rule, catalog) if err != nil { return nil, nil, nil, nil, fmt.Errorf("source %q: %w", source, err) } if len(selectedSet) > 0 { if key, ok := artifacts.ConfiguredArtifactName(source); ok { if _, selected := selectedSet[key]; !selected { - skippedUnselected = append(skippedUnselected, archiveSkippedUnselectedPromotion{ + skippedUnselectedOutputs = append(skippedUnselectedOutputs, publishSkippedUnselectedOutput{ Source: source, Dest: dest, Required: required, @@ -381,29 +381,29 @@ func resolveArchivePromotions( resolved, err := artifacts.ResolveSessionArtifactWithCatalog(paths, m, source, catalog) if err != nil { if locked { - lockedPromotions = append(lockedPromotions, archiveLockedPromotion{ + lockedOutputs = append(lockedOutputs, publishLockedOutput{ Source: source, Dest: dest, - RemoteKey: artifacts.S3PromotedArtifactKey(sessionPrefix, dest), + RemoteKey: artifacts.S3PublishedOutputKey(sessionPrefix, dest), Reason: strings.TrimSpace(lock.Reason), Required: required, }) continue } if errors.Is(err, artifacts.ErrSessionArtifactNotFound) && !required { - skippedOptional = append(skippedOptional, dest) + skippedOptionalOutputs = append(skippedOptionalOutputs, dest) continue } if errors.Is(err, artifacts.ErrSessionArtifactNotFound) { - return nil, nil, nil, nil, fmt.Errorf("required promotion source unavailable: %q", source) + return nil, nil, nil, nil, fmt.Errorf("required output source unavailable: %q", source) } return nil, nil, nil, nil, fmt.Errorf("resolve source %q: %w", source, err) } if locked { - lockedPromotions = append(lockedPromotions, archiveLockedPromotion{ + lockedOutputs = append(lockedOutputs, publishLockedOutput{ Source: source, Dest: dest, - RemoteKey: artifacts.S3PromotedArtifactKey(sessionPrefix, dest), + RemoteKey: artifacts.S3PublishedOutputKey(sessionPrefix, dest), Reason: strings.TrimSpace(lock.Reason), Required: required, LocalPath: resolved.Path, @@ -411,7 +411,7 @@ func resolveArchivePromotions( }) continue } - out = append(out, archivePromotion{ + out = append(out, publishOutput{ Source: source, Dest: dest, Required: required, @@ -419,7 +419,7 @@ func resolveArchivePromotions( Provenance: resolved.Provenance, }) } - return out, skippedOptional, skippedUnselected, lockedPromotions, nil + return out, skippedOptionalOutputs, skippedUnselectedOutputs, lockedOutputs, nil } func archiveSelectedArtifactSet(selected []string) map[string]struct{} { @@ -437,8 +437,8 @@ func archiveSelectedArtifactSet(selected []string) map[string]struct{} { return out } -func archiveLockSet(locks []config.ArchiveLockRule) map[string]config.ArchiveLockRule { - out := make(map[string]config.ArchiveLockRule, len(locks)) +func archiveLockSet(locks []config.PublishLockRule) map[string]config.PublishLockRule { + out := make(map[string]config.PublishLockRule, len(locks)) for _, lock := range locks { source := strings.TrimSpace(lock.Source) if source == "" { @@ -451,7 +451,7 @@ func archiveLockSet(locks []config.ArchiveLockRule) map[string]config.ArchiveLoc return out } -func resolveArchivePromotionDest(rule config.ArchivePromotionRule, catalog *artifacts.ArtifactCatalog) (string, error) { +func resolvePublishOutputDest(rule config.PublishOutputRule, catalog *artifacts.ArtifactCatalog) (string, error) { dest := strings.TrimSpace(rule.Dest) if dest == "" { entry, ok := catalog.Lookup(strings.TrimSpace(rule.Source)) @@ -760,36 +760,36 @@ func writeCurrentRunIDPointer(runID string) (string, error) { func archiveMetadataPreview( bucket, runPrefix, sessionPrefix string, runUploaded []string, - promotedUploaded []string, + publishedUploaded []string, previousUploaded []string, - skippedOptional []string, - skippedUnselected []archiveSkippedUnselectedPromotion, - lockedPromotions []archiveLockedPromotion, + skippedOptionalOutputs []string, + skippedUnselectedOutputs []publishSkippedUnselectedOutput, + lockedOutputs []publishLockedOutput, currentManifestKey string, ) map[string]any { return map[string]any{ - "stage": "publish", - "uploaded": true, - "s3_bucket": bucket, - "s3_run_prefix": runPrefix, - "run_files_uploaded": len(runUploaded), - "run_uploaded_paths": append([]string(nil), runUploaded...), - "promoted_files_uploaded": len(promotedUploaded), - "promoted_paths": append([]string(nil), promotedUploaded...), - "previous_files_uploaded": len(previousUploaded), - "previous_uploaded_paths": append([]string(nil), previousUploaded...), - "skipped_optional_promotions": append([]string(nil), skippedOptional...), - "skipped_unselected_promotions": skippedUnselectedPromotionMetadata(skippedUnselected), - "locked_promotion_count": len(lockedPromotions), - "locked_promotions": lockedPromotionMetadata(lockedPromotions), - "current_manifest_key": currentManifestKey, - "current_run_id_key": artifacts.S3CurrentRunPointerKey(sessionPrefix), - "current_pointer_written": false, - "audio_upload_skipped": true, + "stage": "publish", + "uploaded": true, + "s3_bucket": bucket, + "s3_run_prefix": runPrefix, + "run_files_uploaded": len(runUploaded), + "run_uploaded_paths": append([]string(nil), runUploaded...), + "published_files_uploaded": len(publishedUploaded), + "published_paths": append([]string(nil), publishedUploaded...), + "previous_files_uploaded": len(previousUploaded), + "previous_uploaded_paths": append([]string(nil), previousUploaded...), + "skipped_optional_outputs": append([]string(nil), skippedOptionalOutputs...), + "skipped_unselected_outputs": skippedUnselectedOutputMetadata(skippedUnselectedOutputs), + "locked_output_count": len(lockedOutputs), + "locked_outputs": lockedOutputMetadata(lockedOutputs), + "current_manifest_key": currentManifestKey, + "current_run_id_key": artifacts.S3CurrentRunPointerKey(sessionPrefix), + "current_pointer_written": false, + "audio_upload_skipped": true, } } -func skippedUnselectedPromotionMetadata(skipped []archiveSkippedUnselectedPromotion) []map[string]any { +func skippedUnselectedOutputMetadata(skipped []publishSkippedUnselectedOutput) []map[string]any { out := make([]map[string]any, 0, len(skipped)) for _, item := range skipped { out = append(out, map[string]any{ @@ -801,7 +801,7 @@ func skippedUnselectedPromotionMetadata(skipped []archiveSkippedUnselectedPromot return out } -func lockedPromotionMetadata(locked []archiveLockedPromotion) []map[string]any { +func lockedOutputMetadata(locked []publishLockedOutput) []map[string]any { out := make([]map[string]any, 0, len(locked)) for _, item := range locked { out = append(out, map[string]any{ diff --git a/internal/stage/archive_test.go b/internal/stage/archive_test.go index 518d816..7b577c1 100644 --- a/internal/stage/archive_test.go +++ b/internal/stage/archive_test.go @@ -128,8 +128,8 @@ func TestArchiveUploadsRunRecordPromotionsAndCurrentPointer(t *testing.T) { if result.Metadata["current_pointer_written"] != true { t.Fatalf("metadata = %#v, want current_pointer_written=true", result.Metadata) } - if result.Metadata["promoted_files_uploaded"] != 2 { - t.Fatalf("metadata promoted_files_uploaded = %#v, want 2", result.Metadata["promoted_files_uploaded"]) + if result.Metadata["published_files_uploaded"] != 2 { + t.Fatalf("metadata published_files_uploaded = %#v, want 2", result.Metadata["published_files_uploaded"]) } if result.Metadata["previous_files_uploaded"] != 0 { t.Fatalf("metadata previous_files_uploaded = %#v, want 0", result.Metadata["previous_files_uploaded"]) @@ -179,7 +179,7 @@ func TestArchiveToleratesMissingPreviousCache(t *testing.T) { func TestArchiveUsesCustomPromotionRules(t *testing.T) { env, m, _ := archiveFixture(t) - env.Config.Pipeline.Publish.PromoteArtifacts = []config.ArchivePromotionRule{ + env.Config.Pipeline.Publish.Outputs = []config.PublishOutputRule{ {Source: "narratio.transcript.final_trimmed", Dest: "published/trimmed.json", Required: boolPtr(true)}, {Source: "narratio.artifact.session_recap", Dest: "published/recap.md", Required: boolPtr(true)}, } @@ -200,7 +200,7 @@ func TestArchiveUsesCustomPromotionRules(t *testing.T) { func TestArchiveSkipsOptionalMissingPromotion(t *testing.T) { env, m, _ := archiveFixture(t) - env.Config.Pipeline.Publish.PromoteArtifacts = []config.ArchivePromotionRule{ + env.Config.Pipeline.Publish.Outputs = []config.PublishOutputRule{ {Source: "narratio.transcript.final_trimmed", Dest: "transcripts/final.trimmed.json", Required: boolPtr(true)}, {Source: "narratio.transcript.base", Dest: "transcripts/base.json", Required: boolPtr(false)}, } @@ -209,10 +209,10 @@ func TestArchiveSkipsOptionalMissingPromotion(t *testing.T) { if err != nil { t.Fatalf("Run() error = %v", err) } - got, _ := result.Metadata["skipped_optional_promotions"].([]string) + got, _ := result.Metadata["skipped_optional_outputs"].([]string) want := []string{"transcripts/base.json"} if !reflect.DeepEqual(got, want) { - t.Fatalf("skipped_optional_promotions = %#v, want %#v", got, want) + t.Fatalf("skipped_optional_outputs = %#v, want %#v", got, want) } } @@ -236,15 +236,15 @@ func TestArchiveSkipsRequiredUnselectedConfiguredPromotion(t *testing.T) { if _, ok := fake.Objects[m.S3SessionPrefix+"artifacts/session_recap.md"]; ok { t.Fatalf("unexpected unselected recap promotion upload") } - skipped := result.Metadata["skipped_unselected_promotions"].([]map[string]any) + skipped := result.Metadata["skipped_unselected_outputs"].([]map[string]any) if len(skipped) != 1 { - t.Fatalf("skipped_unselected_promotions = %#v, want one item", skipped) + t.Fatalf("skipped_unselected_outputs = %#v, want one item", skipped) } if skipped[0]["source"] != "narratio.artifact.session_recap" || skipped[0]["dest"] != "artifacts/session_recap.md" || skipped[0]["required"] != true { - t.Fatalf("skipped_unselected_promotions[0] = %#v, want session recap", skipped[0]) + t.Fatalf("skipped_unselected_outputs[0] = %#v, want session recap", skipped[0]) } - if result.Metadata["locked_promotion_count"] != 0 { - t.Fatalf("locked_promotion_count = %#v, want 0", result.Metadata["locked_promotion_count"]) + if result.Metadata["locked_output_count"] != 0 { + t.Fatalf("locked_output_count = %#v, want 0", result.Metadata["locked_output_count"]) } if _, ok := fake.Objects[m.S3SessionPrefix+"current/run_id.txt"]; !ok { t.Fatalf("missing current pointer") @@ -264,15 +264,15 @@ func TestArchiveSelectedConfiguredPromotionStillFailsWhenMissing(t *testing.T) { } _, err := archiveStage{}.Run(context.Background(), env, m) - if err == nil || !strings.Contains(err.Error(), `required promotion source unavailable: "narratio.artifact.session_recap"`) { - t.Fatalf("Run() error = %v, want required selected promotion failure", err) + if err == nil || !strings.Contains(err.Error(), `required output source unavailable: "narratio.artifact.session_recap"`) { + t.Fatalf("Run() error = %v, want required selected output failure", err) } } func TestArchiveLockedSelectedPromotionSkipsAsLocked(t *testing.T) { env, m, _ := archiveFixture(t) env.SelectedArtifactKeys = []string{"session_recap"} - env.Config.Pipeline.Publish.Locks = []config.ArchiveLockRule{ + env.Config.Pipeline.Publish.Locks = []config.PublishLockRule{ {Source: "narratio.artifact.session_recap", Reason: "reviewed"}, } fake := env.ObjectStore.(*storage.FakeBackend) @@ -284,12 +284,12 @@ func TestArchiveLockedSelectedPromotionSkipsAsLocked(t *testing.T) { if _, ok := fake.Objects[m.S3SessionPrefix+"artifacts/session_recap.md"]; ok { t.Fatalf("unexpected locked recap promotion upload") } - if result.Metadata["locked_promotion_count"] != 1 { - t.Fatalf("locked_promotion_count = %#v, want 1", result.Metadata["locked_promotion_count"]) + if result.Metadata["locked_output_count"] != 1 { + t.Fatalf("locked_output_count = %#v, want 1", result.Metadata["locked_output_count"]) } - skipped := result.Metadata["skipped_unselected_promotions"].([]map[string]any) + skipped := result.Metadata["skipped_unselected_outputs"].([]map[string]any) if len(skipped) != 0 { - t.Fatalf("skipped_unselected_promotions = %#v, want empty", skipped) + t.Fatalf("skipped_unselected_outputs = %#v, want empty", skipped) } } @@ -301,7 +301,7 @@ func TestArchiveLockedUnselectedConfiguredPromotionSkipsAsUnselected(t *testing. PromptID: "dnd.player_handout", OutputPath: "artifacts/player_handout.md", } - env.Config.Pipeline.Publish.Locks = []config.ArchiveLockRule{ + env.Config.Pipeline.Publish.Locks = []config.PublishLockRule{ {Source: "narratio.artifact.session_recap", Reason: "reviewed"}, } @@ -309,18 +309,18 @@ func TestArchiveLockedUnselectedConfiguredPromotionSkipsAsUnselected(t *testing. if err != nil { t.Fatalf("Run() error = %v", err) } - if result.Metadata["locked_promotion_count"] != 0 { - t.Fatalf("locked_promotion_count = %#v, want 0", result.Metadata["locked_promotion_count"]) + if result.Metadata["locked_output_count"] != 0 { + t.Fatalf("locked_output_count = %#v, want 0", result.Metadata["locked_output_count"]) } - skipped := result.Metadata["skipped_unselected_promotions"].([]map[string]any) + skipped := result.Metadata["skipped_unselected_outputs"].([]map[string]any) if len(skipped) != 1 || skipped[0]["source"] != "narratio.artifact.session_recap" { - t.Fatalf("skipped_unselected_promotions = %#v, want unselected recap", skipped) + t.Fatalf("skipped_unselected_outputs = %#v, want unselected recap", skipped) } } func TestArchiveSkipsLockedRequiredPromotionAndCommits(t *testing.T) { env, m, _ := archiveFixture(t) - env.Config.Pipeline.Publish.Locks = []config.ArchiveLockRule{ + env.Config.Pipeline.Publish.Locks = []config.PublishLockRule{ {Source: "narratio.transcript.final_trimmed", Reason: "human reviewed"}, } fake := env.ObjectStore.(*storage.FakeBackend) @@ -348,15 +348,15 @@ func TestArchiveSkipsLockedRequiredPromotionAndCommits(t *testing.T) { t.Fatalf("last upload = %#v, want current run pointer %q", fake.Uploads, currentRunIDKey) } - if result.Metadata["promoted_files_uploaded"] != 1 { - t.Fatalf("metadata promoted_files_uploaded = %#v, want 1", result.Metadata["promoted_files_uploaded"]) + if result.Metadata["published_files_uploaded"] != 1 { + t.Fatalf("metadata published_files_uploaded = %#v, want 1", result.Metadata["published_files_uploaded"]) } - if result.Metadata["locked_promotion_count"] != 1 { - t.Fatalf("metadata locked_promotion_count = %#v, want 1", result.Metadata["locked_promotion_count"]) + if result.Metadata["locked_output_count"] != 1 { + t.Fatalf("metadata locked_output_count = %#v, want 1", result.Metadata["locked_output_count"]) } - locked := result.Metadata["locked_promotions"].([]map[string]any) + locked := result.Metadata["locked_outputs"].([]map[string]any) if len(locked) != 1 { - t.Fatalf("locked_promotions = %#v, want one item", locked) + t.Fatalf("locked_outputs = %#v, want one item", locked) } if locked[0]["source"] != "narratio.transcript.final_trimmed" || locked[0]["dest"] != "transcripts/final.trimmed.json" || @@ -376,21 +376,21 @@ func TestArchiveSkipsLockedRequiredPromotionAndCommits(t *testing.T) { stages := current["stages"].(map[string]any) archive := stages["publish"].(map[string]any) meta := archive["metadata"].(map[string]any) - if meta["locked_promotion_count"] != float64(1) { - t.Fatalf("current manifest locked_promotion_count = %#v, want 1", meta["locked_promotion_count"]) + if meta["locked_output_count"] != float64(1) { + t.Fatalf("current manifest locked_output_count = %#v, want 1", meta["locked_output_count"]) } - items := meta["locked_promotions"].([]any) + items := meta["locked_outputs"].([]any) if len(items) != 1 { - t.Fatalf("current manifest locked_promotions = %#v, want one item", items) + t.Fatalf("current manifest locked_outputs = %#v, want one item", items) } } func TestArchiveLockedRequiredMissingPromotionSucceeds(t *testing.T) { env, m, _ := archiveFixture(t) - env.Config.Pipeline.Publish.PromoteArtifacts = []config.ArchivePromotionRule{ + env.Config.Pipeline.Publish.Outputs = []config.PublishOutputRule{ {Source: "narratio.transcript.base", Dest: "transcripts/base.json", Required: boolPtr(true)}, } - env.Config.Pipeline.Publish.Locks = []config.ArchiveLockRule{ + env.Config.Pipeline.Publish.Locks = []config.PublishLockRule{ {Source: "narratio.transcript.base", Reason: "manual merge is locked"}, } @@ -407,9 +407,9 @@ func TestArchiveLockedRequiredMissingPromotionSucceeds(t *testing.T) { if _, ok := fake.Objects[m.S3SessionPrefix+"current/run_id.txt"]; !ok { t.Fatalf("current run pointer should be written for locked missing promotion") } - locked := result.Metadata["locked_promotions"].([]map[string]any) + locked := result.Metadata["locked_outputs"].([]map[string]any) if len(locked) != 1 { - t.Fatalf("locked_promotions = %#v, want one item", locked) + t.Fatalf("locked_outputs = %#v, want one item", locked) } if locked[0]["local_path"] != "" || locked[0]["provenance"] != "" { t.Fatalf("locked missing promotion metadata = %#v, want empty local path/provenance", locked[0]) @@ -418,7 +418,7 @@ func TestArchiveLockedRequiredMissingPromotionSucceeds(t *testing.T) { func TestArchiveLockDoesNotOverwriteExistingPromotion(t *testing.T) { env, m, _ := archiveFixture(t) - env.Config.Pipeline.Publish.Locks = []config.ArchiveLockRule{ + env.Config.Pipeline.Publish.Locks = []config.PublishLockRule{ {Source: "narratio.transcript.final_trimmed", Reason: "already published"}, } fake := env.ObjectStore.(*storage.FakeBackend) @@ -442,13 +442,13 @@ func TestArchiveLockDoesNotOverwriteExistingPromotion(t *testing.T) { func TestArchiveFailsWhenRequiredPromotionMissing(t *testing.T) { env, m, _ := archiveFixture(t) - env.Config.Pipeline.Publish.PromoteArtifacts = []config.ArchivePromotionRule{ + env.Config.Pipeline.Publish.Outputs = []config.PublishOutputRule{ {Source: "narratio.transcript.base", Dest: "transcripts/base.json", Required: boolPtr(true)}, } _, err := archiveStage{}.Run(context.Background(), env, m) - if err == nil || !strings.Contains(err.Error(), "required promotion source unavailable") { - t.Fatalf("Run() error = %v, want required promotion source unavailable failure", err) + if err == nil || !strings.Contains(err.Error(), "required output source unavailable") { + t.Fatalf("Run() error = %v, want required output source unavailable failure", err) } } @@ -485,8 +485,8 @@ func TestArchiveDoesNotWriteCurrentPointerWhenPromotionUploadFails(t *testing.T) env.ObjectStore = &promotionFailingStore{delegate: fake, failKey: failingKey} _, err := archiveStage{}.Run(context.Background(), env, m) - if err == nil || !strings.Contains(err.Error(), "promoted output") { - t.Fatalf("Run() error = %v, want promotion upload failure", err) + if err == nil || !strings.Contains(err.Error(), "upload published output source") { + t.Fatalf("Run() error = %v, want published output upload failure", err) } if _, ok := fake.Objects[m.S3SessionPrefix+"current/run_id.txt"]; ok { t.Fatalf("unexpected current pointer write on promotion failure") @@ -563,10 +563,10 @@ func archiveFixture(t *testing.T) (*Env, *manifest.Manifest, string) { RootPrefix: "dnd", }, }, - Publish: &config.ArchiveConfig{ + Publish: &config.PublishConfig{ Enabled: boolPtr(true), UploadRun: boolPtr(true), - PromoteArtifacts: []config.ArchivePromotionRule{ + Outputs: []config.PublishOutputRule{ {Source: "narratio.transcript.final_trimmed", Dest: "transcripts/final.trimmed.json", Required: boolPtr(true)}, {Source: "narratio.artifact.session_recap", Dest: "artifacts/session_recap.md", Required: boolPtr(true)}, }, diff --git a/internal/stage/merge.go b/internal/stage/merge.go index 5d096ee..dcba85a 100644 --- a/internal/stage/merge.go +++ b/internal/stage/merge.go @@ -150,7 +150,7 @@ func (mergeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*Sta } } - promotedMerged, err := promoteRunLocalOutput(env.ArtifactStore, finalMergedPath, canonicalMergedPath, artifacts.Ref{ + materializedMerged, err := materializeRunLocalOutput(env.ArtifactStore, finalMergedPath, canonicalMergedPath, artifacts.Ref{ Kind: "transcript_base", Category: "transcripts", SessionID: sessionID, @@ -158,9 +158,9 @@ func (mergeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*Sta if err != nil { return nil, fmt.Errorf("merge: promote merged transcript: %w", err) } - outputs := []artifacts.Ref{promotedMerged} + outputs := []artifacts.Ref{materializedMerged} if reportEnabled { - promotedReport, err := promoteRunLocalOutput(env.ArtifactStore, finalReportPath, canonicalReportPath, artifacts.Ref{ + materializedReport, err := materializeRunLocalOutput(env.ArtifactStore, finalReportPath, canonicalReportPath, artifacts.Ref{ Kind: "seriatim_report", Category: "artifacts", SessionID: sessionID, @@ -168,7 +168,7 @@ func (mergeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*Sta if err != nil { return nil, fmt.Errorf("merge: promote report: %w", err) } - outputs = append(outputs, promotedReport) + outputs = append(outputs, materializedReport) } coalesceGap := any(nil) diff --git a/internal/stage/merge_test.go b/internal/stage/merge_test.go index 5b4cd5b..7e735e6 100644 --- a/internal/stage/merge_test.go +++ b/internal/stage/merge_test.go @@ -315,10 +315,10 @@ func TestMergeStageUsesRunLocalPathsAndPromotesCanonical(t *testing.T) { t.Fatalf("run output path = %q, want run-local path", req.OutputMergedTranscriptPath) } if len(result.Outputs) == 0 { - t.Fatalf("outputs = %#v, want promoted outputs", result.Outputs) + t.Fatalf("outputs = %#v, want materialized outputs", result.Outputs) } if strings.Contains(result.Outputs[0].AbsolutePath, string(filepath.Separator)+"runs"+string(filepath.Separator)) { - t.Fatalf("promoted output path = %q, want canonical session path", result.Outputs[0].AbsolutePath) + t.Fatalf("materialized output path = %q, want canonical session path", result.Outputs[0].AbsolutePath) } } diff --git a/internal/stage/normalize.go b/internal/stage/normalize.go index 3e4cb06..13f44d9 100644 --- a/internal/stage/normalize.go +++ b/internal/stage/normalize.go @@ -133,7 +133,7 @@ func (normalizeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) ( } } - promotedNormalized, err := promoteRunLocalOutput(env.ArtifactStore, finalNormalizedPath, canonicalNormalizedPath, artifacts.Ref{ + materializedNormalized, err := materializeRunLocalOutput(env.ArtifactStore, finalNormalizedPath, canonicalNormalizedPath, artifacts.Ref{ Kind: "transcript_final", Category: "transcripts", SessionID: sessionID, @@ -141,9 +141,9 @@ func (normalizeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) ( if err != nil { return nil, fmt.Errorf("normalize: promote normalized transcript: %w", err) } - outputs := []artifacts.Ref{promotedNormalized} + outputs := []artifacts.Ref{materializedNormalized} if reportEnabled { - promotedReport, err := promoteRunLocalOutput(env.ArtifactStore, finalReportPath, canonicalReportPath, artifacts.Ref{ + materializedReport, err := materializeRunLocalOutput(env.ArtifactStore, finalReportPath, canonicalReportPath, artifacts.Ref{ Kind: "seriatim_normalize_report", Category: "artifacts", SessionID: sessionID, @@ -151,7 +151,7 @@ func (normalizeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) ( if err != nil { return nil, fmt.Errorf("normalize: promote report: %w", err) } - outputs = append(outputs, promotedReport) + outputs = append(outputs, materializedReport) } reportCanonicalPath := "" if reportEnabled { diff --git a/internal/stage/normalize_test.go b/internal/stage/normalize_test.go index 5527bad..53323f0 100644 --- a/internal/stage/normalize_test.go +++ b/internal/stage/normalize_test.go @@ -231,10 +231,10 @@ func TestNormalizeStageUsesRunLocalPathsAndPromotesCanonical(t *testing.T) { t.Fatalf("run output path = %q, want run-local path", req.OutputNormalizedPath) } if len(result.Outputs) == 0 { - t.Fatalf("outputs = %#v, want promoted outputs", result.Outputs) + t.Fatalf("outputs = %#v, want materialized outputs", result.Outputs) } if strings.Contains(result.Outputs[0].AbsolutePath, string(filepath.Separator)+"runs"+string(filepath.Separator)) { - t.Fatalf("promoted output path = %q, want canonical session path", result.Outputs[0].AbsolutePath) + t.Fatalf("materialized output path = %q, want canonical session path", result.Outputs[0].AbsolutePath) } } diff --git a/internal/stage/placeholders_test.go b/internal/stage/placeholders_test.go index 435f43b..472385e 100644 --- a/internal/stage/placeholders_test.go +++ b/internal/stage/placeholders_test.go @@ -61,7 +61,7 @@ func TestStagesReturnExpectedMetadata(t *testing.T) { RootPrefix: "dnd", }, }, - Publish: &config.ArchiveConfig{ + Publish: &config.PublishConfig{ Enabled: boolPtr(true), UploadRun: boolPtr(true), }, diff --git a/internal/stage/polish.go b/internal/stage/polish.go index 8af1145..0490379 100644 --- a/internal/stage/polish.go +++ b/internal/stage/polish.go @@ -149,7 +149,7 @@ func (polishStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*St } } - promotedProcessed, err := promoteRunLocalOutput(env.ArtifactStore, finalProcessedPath, canonicalProcessedPath, artifacts.Ref{ + materializedProcessed, err := materializeRunLocalOutput(env.ArtifactStore, finalProcessedPath, canonicalProcessedPath, artifacts.Ref{ Kind: "transcript_polished", Category: "transcripts", SessionID: sessionID, @@ -157,9 +157,9 @@ func (polishStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*St if err != nil { return nil, fmt.Errorf("polish: promote processed transcript: %w", err) } - outputs := []artifacts.Ref{promotedProcessed} + outputs := []artifacts.Ref{materializedProcessed} if reportEnabled { - promotedReport, err := promoteRunLocalOutput(env.ArtifactStore, finalReportPath, canonicalReportPath, artifacts.Ref{ + materializedReport, err := materializeRunLocalOutput(env.ArtifactStore, finalReportPath, canonicalReportPath, artifacts.Ref{ Kind: "audita_report", Category: "artifacts", SessionID: sessionID, @@ -167,7 +167,7 @@ func (polishStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*St if err != nil { return nil, fmt.Errorf("polish: promote report: %w", err) } - outputs = append(outputs, promotedReport) + outputs = append(outputs, materializedReport) } var validationConcurrency any diff --git a/internal/stage/polish_test.go b/internal/stage/polish_test.go index 12742f1..b110fa1 100644 --- a/internal/stage/polish_test.go +++ b/internal/stage/polish_test.go @@ -261,10 +261,10 @@ func TestPolishStageUsesRunLocalPathsAndPromotesCanonical(t *testing.T) { t.Fatalf("run output path = %q, want run-local path", req.OutputProcessedPath) } if len(result.Outputs) == 0 { - t.Fatalf("outputs = %#v, want promoted outputs", result.Outputs) + t.Fatalf("outputs = %#v, want materialized outputs", result.Outputs) } if strings.Contains(result.Outputs[0].AbsolutePath, string(filepath.Separator)+"runs"+string(filepath.Separator)) { - t.Fatalf("promoted output path = %q, want canonical session path", result.Outputs[0].AbsolutePath) + t.Fatalf("materialized output path = %q, want canonical session path", result.Outputs[0].AbsolutePath) } } diff --git a/internal/stage/prepare_previous_test.go b/internal/stage/prepare_previous_test.go index 304215f..7c03fb0 100644 --- a/internal/stage/prepare_previous_test.go +++ b/internal/stage/prepare_previous_test.go @@ -300,7 +300,7 @@ func seedPreviousCurrentState( }) } - artifactKey := artifacts.S3PromotedArtifactKey(previousSessionPrefix, "artifacts/session_recap.md") + artifactKey := artifacts.S3PublishedOutputKey(previousSessionPrefix, "artifacts/session_recap.md") if options.includeArtifactObject { body := options.artifactBody if body == "" { @@ -365,9 +365,9 @@ func buildPreviousManifestForSeed( LocalPath: artifactPath, }, }) - m.MarkStageSucceeded("archive", now, nil) - m.Stages["archive"].Metadata = map[string]any{ - "promoted_paths": []string{"artifacts/session_recap.md"}, + m.MarkStageSucceeded("publish", now, nil) + m.Stages["publish"].Metadata = map[string]any{ + "published_paths": []string{"artifacts/session_recap.md"}, } data, err := json.MarshalIndent(m, "", " ") if err != nil { diff --git a/internal/stage/run_local.go b/internal/stage/run_local.go index b290da2..f55821d 100644 --- a/internal/stage/run_local.go +++ b/internal/stage/run_local.go @@ -110,7 +110,7 @@ func runLocalPathForCanonical(layout runStageLayout, sessionPaths artifacts.Sess return localPath, nil } -func promoteRunLocalOutput( +func materializeRunLocalOutput( store artifacts.Store, srcPath, canonicalPath string, ref artifacts.Ref, @@ -128,11 +128,11 @@ func promoteRunLocalOutput( return artifacts.Ref{}, fmt.Errorf("read run-local output %q: %w", srcPath, err) } if err := store.WriteFileAtomic(canonicalPath, data, 0o644); err != nil { - return artifacts.Ref{}, fmt.Errorf("promote output to %q: %w", canonicalPath, err) + return artifacts.Ref{}, fmt.Errorf("materialize output to %q: %w", canonicalPath, err) } checksum, err := store.Checksum(canonicalPath) if err != nil { - return artifacts.Ref{}, fmt.Errorf("checksum promoted output %q: %w", canonicalPath, err) + return artifacts.Ref{}, fmt.Errorf("checksum materialized output %q: %w", canonicalPath, err) } ref.AbsolutePath = canonicalPath ref.Checksum = checksum diff --git a/internal/stage/transcribe.go b/internal/stage/transcribe.go index 46307ca..a94db87 100644 --- a/internal/stage/transcribe.go +++ b/internal/stage/transcribe.go @@ -213,11 +213,11 @@ dispatch: ref := outputRef[speaker] runOutputPaths = append(runOutputPaths, ref.AbsolutePath) canonicalOut := filepath.Join(paths.TranscriptsRawDir, speaker+".json") - promoted, err := promoteRunLocalOutput(env.ArtifactStore, ref.AbsolutePath, canonicalOut, ref) + materialized, err := materializeRunLocalOutput(env.ArtifactStore, ref.AbsolutePath, canonicalOut, ref) if err != nil { - return nil, fmt.Errorf("transcribe: promote %q output: %w", speaker, err) + return nil, fmt.Errorf("transcribe: materialize %q output: %w", speaker, err) } - outputs = append(outputs, promoted) + outputs = append(outputs, materialized) outputPaths = append(outputPaths, canonicalOut) orderedPerFile[speaker] = perFile[speaker] } diff --git a/internal/stage/transcribe_test.go b/internal/stage/transcribe_test.go index 5dd1fb4..f4b07a0 100644 --- a/internal/stage/transcribe_test.go +++ b/internal/stage/transcribe_test.go @@ -217,7 +217,7 @@ func TestTranscribeStageUsesRunLocalOutputAndPromotesCanonical(t *testing.T) { t.Fatalf("outputs = %#v, want one output", result.Outputs) } if strings.Contains(result.Outputs[0].AbsolutePath, string(filepath.Separator)+"runs"+string(filepath.Separator)) { - t.Fatalf("promoted output path = %q, want canonical session path", result.Outputs[0].AbsolutePath) + t.Fatalf("materialized output path = %q, want canonical session path", result.Outputs[0].AbsolutePath) } } diff --git a/internal/stage/trim.go b/internal/stage/trim.go index c4cee83..8d759f2 100644 --- a/internal/stage/trim.go +++ b/internal/stage/trim.go @@ -100,7 +100,7 @@ func (trimStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*Stag if err := validateProcessedTranscriptOutput(trimmedPath); err != nil { return nil, fmt.Errorf("trim: copied trimmed transcript %q invalid: %w", trimmedPath, err) } - promotedTrimmed, err := promoteRunLocalOutput(env.ArtifactStore, trimmedPath, canonicalTrimmedPath, artifacts.Ref{ + materializedTrimmed, err := materializeRunLocalOutput(env.ArtifactStore, trimmedPath, canonicalTrimmedPath, artifacts.Ref{ Kind: "transcript_final_trimmed", Category: "transcripts", SessionID: sessionID, @@ -110,7 +110,7 @@ func (trimStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*Stag } metadata["trim_action"] = "copy_disabled" return &StageResult{ - Outputs: []artifacts.Ref{promotedTrimmed}, + Outputs: []artifacts.Ref{materializedTrimmed}, Metadata: metadata, }, nil } @@ -351,7 +351,7 @@ func (trimStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*Stag return nil, fmt.Errorf("trim: trimmed transcript %q invalid: %w", trimmedPath, err) } - promotedTrimmed, err := promoteRunLocalOutput(env.ArtifactStore, trimmedPath, canonicalTrimmedPath, artifacts.Ref{ + materializedTrimmed, err := materializeRunLocalOutput(env.ArtifactStore, trimmedPath, canonicalTrimmedPath, artifacts.Ref{ Kind: "transcript_final_trimmed", Category: "transcripts", SessionID: sessionID, @@ -359,7 +359,7 @@ func (trimStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*Stag if err != nil { return nil, fmt.Errorf("trim: promote trimmed transcript: %w", err) } - promotedBounds, err := promoteRunLocalOutput(env.ArtifactStore, finalBoundsOutputPath, canonicalBoundsOutputPath, artifacts.Ref{ + materializedBounds, err := materializeRunLocalOutput(env.ArtifactStore, finalBoundsOutputPath, canonicalBoundsOutputPath, artifacts.Ref{ Kind: "session_bounds", Category: "artifacts", SessionID: sessionID, @@ -369,7 +369,7 @@ func (trimStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*Stag } return &StageResult{ - Outputs: []artifacts.Ref{promotedTrimmed, promotedBounds}, + Outputs: []artifacts.Ref{materializedTrimmed, materializedBounds}, Logs: logPaths, GeneratedConfigs: generatedConfigs, Metadata: metadata, diff --git a/internal/stage/trim_test.go b/internal/stage/trim_test.go index 4b19dde..6025b93 100644 --- a/internal/stage/trim_test.go +++ b/internal/stage/trim_test.go @@ -326,7 +326,7 @@ func TestTrimStageUsesRunLocalPathsAndPromotesCanonical(t *testing.T) { } for _, out := range result.Outputs { if strings.Contains(out.AbsolutePath, string(filepath.Separator)+"runs"+string(filepath.Separator)) { - t.Fatalf("promoted output path = %q, want canonical session path", out.AbsolutePath) + t.Fatalf("materialized output path = %q, want canonical session path", out.AbsolutePath) } } }