Simplified the output of narratio artifacts list --remote and narratio status --session-id
This commit is contained in:
@@ -132,7 +132,7 @@ Valid stage names:
|
||||
- `--session <path>`
|
||||
- `--session-id <value>`
|
||||
- `--previous-session-id <value>`
|
||||
- `--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 <pipeline.yml>] [--campaign <campaign.yml>] [--session <session.yml>] [--session-id <id>] [--previous-session-id <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=<path>` 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=<path>` when that destination differs from the source's canonical path.
|
||||
|
||||
### `locks`
|
||||
|
||||
|
||||
@@ -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 <id>` uses normal config/session loading, including remote session fallback.
|
||||
- `status --session-id <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 <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.
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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})
|
||||
|
||||
Reference in New Issue
Block a user