From 3022f20beb3a95c761e90d6a3b9d9a65507e8f35 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Thu, 21 May 2026 21:20:41 -0500 Subject: [PATCH] Simplified the output of narratio artifacts list --remote and narratio status --session-id --- docs/cli.md | 5 +-- docs/operations.md | 2 +- internal/app/operator_helpers.go | 53 +++------------------------ internal/app/operator_helpers_test.go | 25 ++++++++++++- 4 files changed, 32 insertions(+), 53 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index 1e5c0e3..4009bfb 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -132,7 +132,7 @@ Valid stage names: - `--session ` - `--session-id ` - `--previous-session-id ` -- `--remote`: check remote object availability. Catalog sections use canonical source paths; the `Promoted` section checks configured archive destinations. +- `--remote`: check remote availability for configured archive promotion destinations. ### `locks` @@ -288,8 +288,7 @@ Syntax: narratio artifacts list [--config ] [--campaign ] [--session ] [--session-id ] [--previous-session-id ] [--remote] ``` -`--remote` checks promoted top-level object availability through the storage adapter. -Catalog sections report canonical source-path availability. The `Promoted` section reports each configured archive promotion destination and includes `dest=` when that destination differs from the source's canonical path. +`--remote` checks promoted top-level object availability through the storage adapter. Remote markers appear only in the `Promoted` section, which reports each configured archive promotion destination and includes `dest=` when that destination differs from the source's canonical path. ### `locks` diff --git a/docs/operations.md b/docs/operations.md index 6587f38..93f37b3 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -253,7 +253,7 @@ Dry-run does not write restore report files. - `status` with no config/session flags still requires explicit `--manifest`. - `status --session-id ` uses normal config/session loading, including remote session fallback. -- `status --session-id ` includes the same remote output availability view as `artifacts list --remote` when storage is configured: catalog sections use canonical source paths, and promoted outputs use configured archive destinations. +- `status --session-id ` includes the same promoted remote output availability view as `artifacts list --remote` when storage is configured. - local and S3 audio input modes are mutually exclusive. - archive publish requires upstream stages through `analyze` to be `succeeded`. - required promotion rules can fail when selected analyze artifacts did not generate a required file path. diff --git a/internal/app/operator_helpers.go b/internal/app/operator_helpers.go index c436899..39c5d84 100644 --- a/internal/app/operator_helpers.go +++ b/internal/app/operator_helpers.go @@ -218,14 +218,12 @@ func Status(ctx context.Context, args []string, out io.Writer) error { All: staticArchiveLocks(cfg), } } - remoteState := map[string]string{} promotedRemoteState := map[string]string{} if store != nil { - remoteState = remoteArtifactAvailability(ctx, cfg, store, catalog) promotedRemoteState = remotePromotionAvailability(ctx, cfg, store, catalog) } fmt.Fprintln(out, "Remote outputs:") - writeArtifactList(out, cfg, catalog, catalogLocks, remoteState, promotedRemoteState) + writeArtifactList(out, cfg, catalog, catalogLocks, promotedRemoteState) } if err != nil { fmt.Fprintf(out, "Archive locks: error: %v\n", err) @@ -390,13 +388,11 @@ func ArtifactsList(ctx context.Context, args []string, out io.Writer) error { if err != nil { return fmt.Errorf("artifacts list: %w", err) } - remoteState := map[string]string{} promotedRemoteState := map[string]string{} if remote && store != nil { - remoteState = remoteArtifactAvailability(ctx, cfg, store, catalog) promotedRemoteState = remotePromotionAvailability(ctx, cfg, store, catalog) } - writeArtifactList(out, cfg, catalog, locks, remoteState, promotedRemoteState) + writeArtifactList(out, cfg, catalog, locks, promotedRemoteState) return nil } @@ -797,7 +793,7 @@ func buildHelperArtifactCatalog(cfg *config.Config) (*artifacts.ArtifactCatalog, return catalog, nil } -func writeArtifactList(out io.Writer, cfg *config.Config, catalog *artifacts.ArtifactCatalog, locks *effectiveLocks, remoteState map[string]string, promotedRemoteState map[string]string) { +func writeArtifactList(out io.Writer, cfg *config.Config, catalog *artifacts.ArtifactCatalog, locks *effectiveLocks, promotedRemoteState map[string]string) { lockSet := lockSourceSet(locks.All) fmt.Fprintln(out, "Built-in:") for _, id := range []string{ @@ -807,11 +803,11 @@ func writeArtifactList(out io.Writer, cfg *config.Config, catalog *artifacts.Art artifacts.ArtifactTranscriptTrimmed, artifacts.ArtifactBoundsSession, } { - writeArtifactLine(out, id, lockSet, remoteState) + writeArtifactLine(out, id, lockSet) } fmt.Fprintln(out, "Configured:") for _, entry := range catalog.ListConfigured() { - writeArtifactLine(out, entry.SourceID, lockSet, remoteState) + writeArtifactLine(out, entry.SourceID, lockSet) } fmt.Fprintln(out, "Previous-session:") for _, req := range artifacts.CollectPreviousArtifactRequirements(configuredScriptoriumArtifacts(cfg)) { @@ -823,14 +819,11 @@ func writeArtifactList(out io.Writer, cfg *config.Config, catalog *artifacts.Art } } -func writeArtifactLine(out io.Writer, source string, lockSet map[string]config.ArchiveLockRule, remoteState map[string]string) { +func writeArtifactLine(out io.Writer, source string, lockSet map[string]config.ArchiveLockRule) { parts := []string{source} if _, ok := lockSet[source]; ok { parts = append(parts, "locked") } - if state := remoteState[source]; state != "" { - parts = append(parts, state) - } fmt.Fprintf(out, "- %s\n", strings.Join(parts, " ")) } @@ -855,26 +848,6 @@ func writePromotedArtifactLine(out io.Writer, rule config.ArchivePromotionRule, fmt.Fprintf(out, "- %s\n", strings.Join(parts, " ")) } -func remoteArtifactAvailability(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 _, source := range allCatalogSources(catalog) { - entry, ok := catalog.Lookup(source) - if !ok || strings.TrimSpace(entry.CanonicalRelPath) == "" { - continue - } - key := artifacts.S3PromotedArtifactKey(sessionPrefix, entry.CanonicalRelPath) - if exists, err := store.Exists(ctx, key); err == nil && exists { - out[source] = "remote=promoted" - } else if err != nil { - out[source] = "remote=error" - } else { - out[source] = "remote=missing" - } - } - return out -} - func remotePromotionAvailability(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) @@ -938,20 +911,6 @@ func promotionRemoteStateKey(source, dest string) string { return strings.TrimSpace(source) + "\x00" + strings.TrimSpace(dest) } -func allCatalogSources(catalog *artifacts.ArtifactCatalog) []string { - out := []string{ - artifacts.ArtifactTranscriptMerged, - artifacts.ArtifactTranscriptPolished, - artifacts.ArtifactTranscriptFull, - artifacts.ArtifactTranscriptTrimmed, - artifacts.ArtifactBoundsSession, - } - for _, entry := range catalog.ListConfigured() { - out = append(out, entry.SourceID) - } - return out -} - func writeLocks(out io.Writer, cfg *config.Config, locks *effectiveLocks) { if locks == nil || len(locks.All) == 0 { fmt.Fprintln(out, "Archive locks: none") diff --git a/internal/app/operator_helpers_test.go b/internal/app/operator_helpers_test.go index 1e8f59c..538df6c 100644 --- a/internal/app/operator_helpers_test.go +++ b/internal/app/operator_helpers_test.go @@ -311,6 +311,12 @@ func TestExecuteTopLevelLockAndUnlockAreRemoved(t *testing.T) { func TestExecuteArtifactsListRemoteReportsPromotedAvailability(t *testing.T) { workspaceRoot := t.TempDir() pipelinePath, campaignPath, sessionPath := writeValidConfigFiles(t, workspaceRoot) + addArchivePromotionsToPipeline(t, pipelinePath, ` + promote_artifacts: + - source: narratio.transcript.trimmed + dest: transcripts/trimmed.json + required: true +`) fake := &storage.FakeBackend{} trimmedKey := artifacts.S3PromotedArtifactKey( artifacts.S3SessionPrefix("dnd", "sample-campaign", "2026-05-03"), @@ -369,9 +375,15 @@ func TestExecuteArtifactsListRemoteUsesPromotionDestinations(t *testing.T) { t.Fatalf("exit code = %d, want 0; stderr=%q", code, stderr.String()) } out := stdout.String() - for _, want := range []string{ + for _, unwanted := range []string{ "narratio.transcript.full remote=missing", "narratio.bounds.session remote=missing", + } { + if strings.Contains(out, unwanted) { + t.Fatalf("stdout = %q, did not want catalog remote marker %q", out, unwanted) + } + } + for _, want := range []string{ "narratio.transcript.full dest=transcripts/full.json remote=promoted", "narratio.bounds.session dest=transcripts/bounds.json remote=promoted", } { @@ -426,19 +438,28 @@ func TestExecuteStatusReportsRemoteArtifactCatalog(t *testing.T) { "Configured:", "Previous-session:", "Promoted:", + "narratio.transcript.trimmed locked", "narratio.transcript.trimmed locked remote=promoted", - "narratio.transcript.merged remote=missing", "narratio.transcript.full dest=transcripts/full.json remote=promoted", } { if !strings.Contains(out, want) { t.Fatalf("stdout = %q, want %q", out, want) } } + if strings.Contains(out, "narratio.transcript.merged remote=missing") { + t.Fatalf("stdout = %q, did not want catalog remote marker", out) + } } func TestExecuteStatusReportsRemoteArtifactCatalogErrorsWithoutFailing(t *testing.T) { workspaceRoot := t.TempDir() pipelinePath, campaignPath, sessionPath := writeValidConfigFiles(t, workspaceRoot) + addArchivePromotionsToPipeline(t, pipelinePath, ` + promote_artifacts: + - source: narratio.transcript.trimmed + dest: transcripts/trimmed.json + required: true +`) fake := &storage.FakeBackend{ExistsErr: fmt.Errorf("exists failed")} var storeInitCalls int restoreAppConfigTestGlobals(t, fake, &storeInitCalls, []string{sessionPath})