Added remote artifact listing to narratio status
This commit is contained in:
@@ -231,6 +231,7 @@ Session output includes:
|
|||||||
- session ID, campaign, workspace, session config source.
|
- session ID, campaign, workspace, session config source.
|
||||||
- local manifest state when present.
|
- local manifest state when present.
|
||||||
- remote current archive state when storage is configured.
|
- remote current archive state when storage is configured.
|
||||||
|
- catalog-based remote output availability for expected transcript and artifact sources.
|
||||||
- effective archive locks and conservative next actions.
|
- effective archive locks and conservative next actions.
|
||||||
|
|
||||||
Common failure cases:
|
Common failure cases:
|
||||||
|
|||||||
@@ -219,6 +219,8 @@ Recommended recovery:
|
|||||||
narratio status --session-id 2026-04-04
|
narratio status --session-id 2026-04-04
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This reports local manifest state, committed remote current state, expected remote transcript/artifact availability, and archive locks.
|
||||||
|
|
||||||
2. for one manifest file, run:
|
2. for one manifest file, run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -251,6 +253,7 @@ Dry-run does not write restore report files.
|
|||||||
|
|
||||||
- `status` with no config/session flags still requires explicit `--manifest`.
|
- `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>` uses normal config/session loading, including remote session fallback.
|
||||||
|
- `status --session-id <id>` includes the same source-based remote output availability view as `artifacts list --remote` when storage is configured.
|
||||||
- local and S3 audio input modes are mutually exclusive.
|
- local and S3 audio input modes are mutually exclusive.
|
||||||
- archive publish requires upstream stages through `analyze` to be `succeeded`.
|
- 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.
|
- required promotion rules can fail when selected analyze artifacts did not generate a required file path.
|
||||||
|
|||||||
@@ -208,6 +208,23 @@ func Status(ctx context.Context, args []string, out io.Writer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
locks, err := loadEffectiveLocks(ctx, cfg, store)
|
locks, err := loadEffectiveLocks(ctx, cfg, store)
|
||||||
|
if catalog, catalogErr := buildHelperArtifactCatalog(cfg); catalogErr != nil {
|
||||||
|
fmt.Fprintf(out, "Remote outputs: error: %v\n", catalogErr)
|
||||||
|
} else if storeErr == nil {
|
||||||
|
catalogLocks := locks
|
||||||
|
if err != nil {
|
||||||
|
catalogLocks = &effectiveLocks{
|
||||||
|
Static: staticArchiveLocks(cfg),
|
||||||
|
All: staticArchiveLocks(cfg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remoteState := map[string]string{}
|
||||||
|
if store != nil {
|
||||||
|
remoteState = remoteArtifactAvailability(ctx, cfg, store, catalog)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(out, "Remote outputs:")
|
||||||
|
writeArtifactList(out, cfg, catalog, catalogLocks, remoteState)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(out, "Archive locks: error: %v\n", err)
|
fmt.Fprintf(out, "Archive locks: error: %v\n", err)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -337,6 +337,80 @@ func TestExecuteArtifactsListRemoteReportsPromotedAvailability(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExecuteStatusReportsRemoteArtifactCatalog(t *testing.T) {
|
||||||
|
workspaceRoot := t.TempDir()
|
||||||
|
pipelinePath, campaignPath, sessionPath := writeValidConfigFiles(t, workspaceRoot)
|
||||||
|
fake := &storage.FakeBackend{}
|
||||||
|
sessionPrefix := artifacts.S3SessionPrefix("dnd", "sample-campaign", "2026-05-03")
|
||||||
|
manifestKey, runIDKey := artifacts.ResolveArchiveCurrentStateKeys(sessionPrefix)
|
||||||
|
trimmedKey := artifacts.S3PromotedArtifactKey(sessionPrefix, "transcripts/trimmed.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")})
|
||||||
|
fake.SeedObject(storage.FakeObject{Key: trimmedKey, Data: []byte(`{"segments":[]}`)})
|
||||||
|
fake.SeedObject(storage.FakeObject{Key: lockKey, Data: []byte("locks:\n - source: narratio.transcript.trimmed\n reason: remote review\n")})
|
||||||
|
var storeInitCalls int
|
||||||
|
restoreAppConfigTestGlobals(t, fake, &storeInitCalls, []string{sessionPath})
|
||||||
|
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
code := Execute([]string{
|
||||||
|
"status",
|
||||||
|
"--config", pipelinePath,
|
||||||
|
"--campaign", campaignPath,
|
||||||
|
"--session", sessionPath,
|
||||||
|
"--session-id", "2026-05-03",
|
||||||
|
}, &stdout, &stderr)
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("exit code = %d, want 0; stderr=%q", code, stderr.String())
|
||||||
|
}
|
||||||
|
out := stdout.String()
|
||||||
|
for _, want := range []string{
|
||||||
|
"Remote outputs:",
|
||||||
|
"Built-in:",
|
||||||
|
"Configured:",
|
||||||
|
"Previous-session:",
|
||||||
|
"Promoted:",
|
||||||
|
"narratio.transcript.trimmed locked remote=promoted",
|
||||||
|
"narratio.transcript.merged remote=missing",
|
||||||
|
} {
|
||||||
|
if !strings.Contains(out, want) {
|
||||||
|
t.Fatalf("stdout = %q, want %q", out, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExecuteStatusReportsRemoteArtifactCatalogErrorsWithoutFailing(t *testing.T) {
|
||||||
|
workspaceRoot := t.TempDir()
|
||||||
|
pipelinePath, campaignPath, sessionPath := writeValidConfigFiles(t, workspaceRoot)
|
||||||
|
fake := &storage.FakeBackend{ExistsErr: fmt.Errorf("exists failed")}
|
||||||
|
var storeInitCalls int
|
||||||
|
restoreAppConfigTestGlobals(t, fake, &storeInitCalls, []string{sessionPath})
|
||||||
|
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
code := Execute([]string{
|
||||||
|
"status",
|
||||||
|
"--config", pipelinePath,
|
||||||
|
"--campaign", campaignPath,
|
||||||
|
"--session", sessionPath,
|
||||||
|
"--session-id", "2026-05-03",
|
||||||
|
}, &stdout, &stderr)
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("exit code = %d, want 0; stderr=%q", code, stderr.String())
|
||||||
|
}
|
||||||
|
out := stdout.String()
|
||||||
|
if !strings.Contains(out, "Remote archive: missing or unavailable:") {
|
||||||
|
t.Fatalf("stdout = %q, want remote archive unavailable state", out)
|
||||||
|
}
|
||||||
|
if !strings.Contains(out, "Remote outputs:") || !strings.Contains(out, "narratio.transcript.trimmed remote=error") {
|
||||||
|
t.Fatalf("stdout = %q, want remote output error state", out)
|
||||||
|
}
|
||||||
|
if !strings.Contains(out, "Archive locks: error:") {
|
||||||
|
t.Fatalf("stdout = %q, want archive locks error", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestExecuteArchiveLoadsRemoteLocks(t *testing.T) {
|
func TestExecuteArchiveLoadsRemoteLocks(t *testing.T) {
|
||||||
workspaceRoot := t.TempDir()
|
workspaceRoot := t.TempDir()
|
||||||
pipelinePath, campaignPath, sessionPath := writeValidArchiveConfigFiles(t, workspaceRoot)
|
pipelinePath, campaignPath, sessionPath := writeValidArchiveConfigFiles(t, workspaceRoot)
|
||||||
|
|||||||
Reference in New Issue
Block a user