Updated transcript artifact names and canonical paths to use a consistent, role-based nomenclature
This commit is contained in:
@@ -9,20 +9,35 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/artifactmodel"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
|
||||
)
|
||||
|
||||
const (
|
||||
ArtifactTranscriptMerged = "narratio.transcript.merged"
|
||||
ArtifactTranscriptPolished = "narratio.transcript.polished"
|
||||
ArtifactTranscriptFull = "narratio.transcript.full"
|
||||
ArtifactTranscriptTrimmed = "narratio.transcript.trimmed"
|
||||
ArtifactBoundsSession = "narratio.bounds.session"
|
||||
ArtifactTranscriptBase = artifactmodel.SourceTranscriptBase
|
||||
ArtifactTranscriptPolished = artifactmodel.SourceTranscriptPolished
|
||||
ArtifactTranscriptFinal = artifactmodel.SourceTranscriptFinal
|
||||
ArtifactTranscriptFinalTrimmed = artifactmodel.SourceTranscriptFinalTrimmed
|
||||
ArtifactBoundsSession = "narratio.bounds.session"
|
||||
|
||||
ArtifactProvenancePreviousCacheManifestInput = "manifest.inputs.previous_cache"
|
||||
ArtifactProvenancePreviousCacheFilesystem = "current_session.previous_cache"
|
||||
)
|
||||
|
||||
const (
|
||||
TranscriptPathBase = artifactmodel.TranscriptPathBase
|
||||
TranscriptPathPolished = artifactmodel.TranscriptPathPolished
|
||||
TranscriptPathFinal = artifactmodel.TranscriptPathFinal
|
||||
TranscriptPathFinalTrimmed = artifactmodel.TranscriptPathFinalTrimmed
|
||||
)
|
||||
|
||||
const (
|
||||
TranscriptOutputKindBase = artifactmodel.TranscriptOutputKindBase
|
||||
TranscriptOutputKindPolished = artifactmodel.TranscriptOutputKindPolished
|
||||
TranscriptOutputKindFinal = artifactmodel.TranscriptOutputKindFinal
|
||||
TranscriptOutputKindFinalTrimmed = artifactmodel.TranscriptOutputKindFinalTrimmed
|
||||
)
|
||||
|
||||
// ErrSessionArtifactNotFound is returned when no readable artifact exists for a known ID.
|
||||
var ErrSessionArtifactNotFound = errors.New("session artifact not found")
|
||||
var configuredArtifactSourceRE = regexp.MustCompile(`^narratio\.artifact\.([a-z][a-z0-9_]*)$`)
|
||||
@@ -44,42 +59,27 @@ type artifactSpec struct {
|
||||
ContentKind artifactContentKind
|
||||
}
|
||||
|
||||
var artifactRegistry = map[string]artifactSpec{
|
||||
ArtifactTranscriptMerged: {
|
||||
ID: ArtifactTranscriptMerged,
|
||||
CanonicalRelPath: "transcripts/merged.json",
|
||||
ProducerStage: "merge",
|
||||
OutputKind: "transcript_merged",
|
||||
ContentKind: contentTranscriptJSON,
|
||||
},
|
||||
ArtifactTranscriptPolished: {
|
||||
ID: ArtifactTranscriptPolished,
|
||||
CanonicalRelPath: "transcripts/processed.json",
|
||||
ProducerStage: "polish",
|
||||
OutputKind: "transcript_processed",
|
||||
ContentKind: contentTranscriptJSON,
|
||||
},
|
||||
ArtifactTranscriptFull: {
|
||||
ID: ArtifactTranscriptFull,
|
||||
CanonicalRelPath: "transcripts/normalized.json",
|
||||
ProducerStage: "normalize",
|
||||
OutputKind: "transcript_normalized",
|
||||
ContentKind: contentTranscriptJSON,
|
||||
},
|
||||
ArtifactTranscriptTrimmed: {
|
||||
ID: ArtifactTranscriptTrimmed,
|
||||
CanonicalRelPath: "transcripts/trimmed.json",
|
||||
ProducerStage: "trim",
|
||||
OutputKind: "transcript_trimmed",
|
||||
ContentKind: contentTranscriptJSON,
|
||||
},
|
||||
ArtifactBoundsSession: {
|
||||
var artifactRegistry = buildArtifactRegistry()
|
||||
|
||||
func buildArtifactRegistry() map[string]artifactSpec {
|
||||
registry := map[string]artifactSpec{}
|
||||
for _, transcript := range RuntimeTranscriptArtifacts() {
|
||||
registry[transcript.SourceID] = artifactSpec{
|
||||
ID: transcript.SourceID,
|
||||
CanonicalRelPath: transcript.CanonicalRelPath,
|
||||
ProducerStage: transcript.ProducerStage,
|
||||
OutputKind: transcript.OutputKind,
|
||||
ContentKind: contentTranscriptJSON,
|
||||
}
|
||||
}
|
||||
registry[ArtifactBoundsSession] = artifactSpec{
|
||||
ID: ArtifactBoundsSession,
|
||||
CanonicalRelPath: "artifacts/session_bounds.json",
|
||||
ProducerStage: "trim",
|
||||
OutputKind: "session_bounds",
|
||||
ContentKind: contentJSON,
|
||||
},
|
||||
}
|
||||
return registry
|
||||
}
|
||||
|
||||
// ResolvedSessionArtifact describes one session-level artifact lookup result.
|
||||
|
||||
@@ -23,7 +23,7 @@ func TestNormalizeSessionArtifactSource(t *testing.T) {
|
||||
{name: "legacy alias normalized unsupported", source: "normalized_transcript", wantErr: "unsupported artifact source"},
|
||||
{name: "legacy alias trimmed unsupported", source: "trimmed_transcript", wantErr: "unsupported artifact source"},
|
||||
{name: "configured source unsupported in built-in normalization", source: "narratio.artifact.session_recap", wantErr: "unsupported artifact source"},
|
||||
{name: "canonical", source: ArtifactTranscriptTrimmed, wantID: ArtifactTranscriptTrimmed},
|
||||
{name: "canonical", source: ArtifactTranscriptFinalTrimmed, wantID: ArtifactTranscriptFinalTrimmed},
|
||||
{name: "unsupported", source: "narratio.unknown", wantErr: "unsupported artifact source"},
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ func TestConfiguredArtifactSourceHelpers(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "built-in",
|
||||
source: ArtifactTranscriptMerged,
|
||||
source: ArtifactTranscriptBase,
|
||||
wantMatch: false,
|
||||
},
|
||||
}
|
||||
@@ -160,7 +160,7 @@ func TestResolveSessionArtifactPrefersManifestOutput(t *testing.T) {
|
||||
if err := os.WriteFile(manifestPath, []byte(`{"segments":[]}`), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile() error = %v", err)
|
||||
}
|
||||
canonicalPath := filepath.Join(paths.TranscriptsDir, "normalized.json")
|
||||
canonicalPath := filepath.Join(paths.TranscriptsDir, "final.json")
|
||||
if err := os.MkdirAll(filepath.Dir(canonicalPath), 0o755); err != nil {
|
||||
t.Fatalf("MkdirAll() error = %v", err)
|
||||
}
|
||||
@@ -170,10 +170,10 @@ func TestResolveSessionArtifactPrefersManifestOutput(t *testing.T) {
|
||||
|
||||
m := manifest.New("session", time.Now().UTC())
|
||||
m.MarkStageSucceeded("normalize", time.Now().UTC(), []manifest.ArtifactRecord{
|
||||
{Kind: "transcript_normalized", LocalPath: manifestPath, ProducerRunID: "run-123"},
|
||||
{Kind: "transcript_final", LocalPath: manifestPath, ProducerRunID: "run-123"},
|
||||
})
|
||||
|
||||
resolved, err := ResolveSessionArtifact(paths, m, ArtifactTranscriptFull)
|
||||
resolved, err := ResolveSessionArtifact(paths, m, ArtifactTranscriptFinal)
|
||||
if err != nil {
|
||||
t.Fatalf("ResolveSessionArtifact() error = %v", err)
|
||||
}
|
||||
@@ -191,7 +191,7 @@ func TestResolveSessionArtifactPrefersManifestOutput(t *testing.T) {
|
||||
func TestResolveSessionArtifactFallsBackToCanonicalPath(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
paths := buildSessionPaths(workspace, "campaign", "session")
|
||||
canonicalPath := filepath.Join(paths.TranscriptsDir, "trimmed.json")
|
||||
canonicalPath := filepath.Join(paths.TranscriptsDir, "final.trimmed.json")
|
||||
if err := os.MkdirAll(filepath.Dir(canonicalPath), 0o755); err != nil {
|
||||
t.Fatalf("MkdirAll() error = %v", err)
|
||||
}
|
||||
@@ -199,7 +199,7 @@ func TestResolveSessionArtifactFallsBackToCanonicalPath(t *testing.T) {
|
||||
t.Fatalf("WriteFile() error = %v", err)
|
||||
}
|
||||
|
||||
resolved, err := ResolveSessionArtifact(paths, nil, ArtifactTranscriptTrimmed)
|
||||
resolved, err := ResolveSessionArtifact(paths, nil, ArtifactTranscriptFinalTrimmed)
|
||||
if err != nil {
|
||||
t.Fatalf("ResolveSessionArtifact() error = %v", err)
|
||||
}
|
||||
@@ -215,7 +215,7 @@ func TestResolveSessionArtifactMissingReturnsTypedError(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
paths := buildSessionPaths(workspace, "campaign", "session")
|
||||
|
||||
_, err := ResolveSessionArtifact(paths, nil, ArtifactTranscriptTrimmed)
|
||||
_, err := ResolveSessionArtifact(paths, nil, ArtifactTranscriptFinalTrimmed)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
@@ -227,7 +227,7 @@ func TestResolveSessionArtifactMissingReturnsTypedError(t *testing.T) {
|
||||
func TestResolveSessionArtifactValidatesTranscriptShape(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
paths := buildSessionPaths(workspace, "campaign", "session")
|
||||
canonicalPath := filepath.Join(paths.TranscriptsDir, "processed.json")
|
||||
canonicalPath := filepath.Join(paths.TranscriptsDir, "polished.json")
|
||||
if err := os.MkdirAll(filepath.Dir(canonicalPath), 0o755); err != nil {
|
||||
t.Fatalf("MkdirAll() error = %v", err)
|
||||
}
|
||||
@@ -247,7 +247,7 @@ func TestResolveSessionArtifactValidatesTranscriptShape(t *testing.T) {
|
||||
func TestResolveSessionArtifactWithCatalogBuiltInBehaviorUnchanged(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
paths := buildSessionPaths(workspace, "campaign", "session")
|
||||
canonicalPath := filepath.Join(paths.TranscriptsDir, "trimmed.json")
|
||||
canonicalPath := filepath.Join(paths.TranscriptsDir, "final.trimmed.json")
|
||||
if err := os.MkdirAll(filepath.Dir(canonicalPath), 0o755); err != nil {
|
||||
t.Fatalf("MkdirAll() error = %v", err)
|
||||
}
|
||||
@@ -255,7 +255,7 @@ func TestResolveSessionArtifactWithCatalogBuiltInBehaviorUnchanged(t *testing.T)
|
||||
t.Fatalf("WriteFile() error = %v", err)
|
||||
}
|
||||
|
||||
resolved, err := ResolveSessionArtifactWithCatalog(paths, nil, ArtifactTranscriptTrimmed, NewArtifactCatalog())
|
||||
resolved, err := ResolveSessionArtifactWithCatalog(paths, nil, ArtifactTranscriptFinalTrimmed, NewArtifactCatalog())
|
||||
if err != nil {
|
||||
t.Fatalf("ResolveSessionArtifactWithCatalog() error = %v", err)
|
||||
}
|
||||
|
||||
@@ -215,10 +215,10 @@ func (c *ArtifactCatalog) addEntry(entry CatalogEntry) error {
|
||||
|
||||
func runtimeBuiltInArtifactIDs() []string {
|
||||
return []string{
|
||||
ArtifactTranscriptMerged,
|
||||
ArtifactTranscriptBase,
|
||||
ArtifactTranscriptPolished,
|
||||
ArtifactTranscriptFull,
|
||||
ArtifactTranscriptTrimmed,
|
||||
ArtifactTranscriptFinal,
|
||||
ArtifactTranscriptFinalTrimmed,
|
||||
ArtifactBoundsSession,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ func TestArtifactCatalogRegisterBuiltInsAndLookup(t *testing.T) {
|
||||
t.Fatalf("RegisterBuiltIns() error = %v", err)
|
||||
}
|
||||
|
||||
entry, ok := catalog.Lookup(ArtifactTranscriptFull)
|
||||
entry, ok := catalog.Lookup(ArtifactTranscriptFinal)
|
||||
if !ok {
|
||||
t.Fatalf("Lookup(%q) ok = false, want true", ArtifactTranscriptFull)
|
||||
t.Fatalf("Lookup(%q) ok = false, want true", ArtifactTranscriptFinal)
|
||||
}
|
||||
if !entry.Planned {
|
||||
t.Fatalf("entry.Planned = false, want true")
|
||||
@@ -18,8 +18,8 @@ func TestArtifactCatalogRegisterBuiltInsAndLookup(t *testing.T) {
|
||||
if entry.Executable {
|
||||
t.Fatalf("entry.Executable = true, want false")
|
||||
}
|
||||
if entry.CanonicalRelPath != "transcripts/normalized.json" {
|
||||
t.Fatalf("entry.CanonicalRelPath = %q, want transcripts/normalized.json", entry.CanonicalRelPath)
|
||||
if entry.CanonicalRelPath != "transcripts/final.json" {
|
||||
t.Fatalf("entry.CanonicalRelPath = %q, want transcripts/final.json", entry.CanonicalRelPath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ func TestCollectPreviousArtifactRequirements(t *testing.T) {
|
||||
"session_recap": {
|
||||
Enabled: true,
|
||||
Inputs: map[string]config.ScriptoriumInputConfig{
|
||||
"transcript": {Source: "narratio.transcript.trimmed", Required: true},
|
||||
"transcript": {Source: "narratio.transcript.final_trimmed", Required: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -43,8 +43,8 @@ func TestS3KeyConstruction(t *testing.T) {
|
||||
t.Fatalf("manifest key = %q", manifestKey)
|
||||
}
|
||||
|
||||
promoted := S3PromotedArtifactKey(sessionPrefix, "transcripts/trimmed.json")
|
||||
if promoted != "dnd/campaigns/forsaken/sessions/2026-04-19/transcripts/trimmed.json" {
|
||||
promoted := S3PromotedArtifactKey(sessionPrefix, "transcripts/final.trimmed.json")
|
||||
if promoted != "dnd/campaigns/forsaken/sessions/2026-04-19/transcripts/final.trimmed.json" {
|
||||
t.Fatalf("promoted key = %q", promoted)
|
||||
}
|
||||
|
||||
|
||||
25
internal/artifacts/transcripts.go
Normal file
25
internal/artifacts/transcripts.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package artifacts
|
||||
|
||||
import "gitea.maximumdirect.net/eric/narratio/internal/artifactmodel"
|
||||
|
||||
type TranscriptArtifactSpec = artifactmodel.TranscriptArtifactSpec
|
||||
|
||||
// RuntimeTranscriptArtifacts returns the current runtime transcript mappings in pipeline order.
|
||||
func RuntimeTranscriptArtifacts() []TranscriptArtifactSpec {
|
||||
return artifactmodel.RuntimeTranscriptArtifacts()
|
||||
}
|
||||
|
||||
// PlannedTranscriptArtifacts returns the target transcript mappings for the transcript naming roadmap.
|
||||
func PlannedTranscriptArtifacts() []TranscriptArtifactSpec {
|
||||
return artifactmodel.RuntimeTranscriptArtifacts()
|
||||
}
|
||||
|
||||
// LookupRuntimeTranscriptArtifact returns current runtime transcript metadata by source ID.
|
||||
func LookupRuntimeTranscriptArtifact(sourceID string) (TranscriptArtifactSpec, bool) {
|
||||
return artifactmodel.LookupRuntimeTranscriptArtifact(sourceID)
|
||||
}
|
||||
|
||||
// LookupPlannedTranscriptArtifact returns target transcript metadata by source ID.
|
||||
func LookupPlannedTranscriptArtifact(sourceID string) (TranscriptArtifactSpec, bool) {
|
||||
return artifactmodel.LookupRuntimeTranscriptArtifact(sourceID)
|
||||
}
|
||||
123
internal/artifacts/transcripts_test.go
Normal file
123
internal/artifacts/transcripts_test.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRuntimeTranscriptArtifacts(t *testing.T) {
|
||||
want := []TranscriptArtifactSpec{
|
||||
{
|
||||
SourceID: ArtifactTranscriptBase,
|
||||
CanonicalRelPath: TranscriptPathBase,
|
||||
ProducerStage: "merge",
|
||||
OutputKind: TranscriptOutputKindBase,
|
||||
},
|
||||
{
|
||||
SourceID: ArtifactTranscriptPolished,
|
||||
CanonicalRelPath: TranscriptPathPolished,
|
||||
ProducerStage: "polish",
|
||||
OutputKind: TranscriptOutputKindPolished,
|
||||
},
|
||||
{
|
||||
SourceID: ArtifactTranscriptFinal,
|
||||
CanonicalRelPath: TranscriptPathFinal,
|
||||
ProducerStage: "normalize",
|
||||
OutputKind: TranscriptOutputKindFinal,
|
||||
},
|
||||
{
|
||||
SourceID: ArtifactTranscriptFinalTrimmed,
|
||||
CanonicalRelPath: TranscriptPathFinalTrimmed,
|
||||
ProducerStage: "trim",
|
||||
OutputKind: TranscriptOutputKindFinalTrimmed,
|
||||
},
|
||||
}
|
||||
|
||||
got := RuntimeTranscriptArtifacts()
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("RuntimeTranscriptArtifacts() = %#v, want %#v", got, want)
|
||||
}
|
||||
|
||||
for _, spec := range want {
|
||||
gotSpec, ok := LookupRuntimeTranscriptArtifact(spec.SourceID)
|
||||
if !ok {
|
||||
t.Fatalf("LookupRuntimeTranscriptArtifact(%q) ok = false, want true", spec.SourceID)
|
||||
}
|
||||
if gotSpec != spec {
|
||||
t.Fatalf("LookupRuntimeTranscriptArtifact(%q) = %#v, want %#v", spec.SourceID, gotSpec, spec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlannedTranscriptArtifacts(t *testing.T) {
|
||||
want := []TranscriptArtifactSpec{
|
||||
{
|
||||
SourceID: ArtifactTranscriptBase,
|
||||
CanonicalRelPath: TranscriptPathBase,
|
||||
ProducerStage: "merge",
|
||||
OutputKind: TranscriptOutputKindBase,
|
||||
},
|
||||
{
|
||||
SourceID: ArtifactTranscriptPolished,
|
||||
CanonicalRelPath: TranscriptPathPolished,
|
||||
ProducerStage: "polish",
|
||||
OutputKind: TranscriptOutputKindPolished,
|
||||
},
|
||||
{
|
||||
SourceID: ArtifactTranscriptFinal,
|
||||
CanonicalRelPath: TranscriptPathFinal,
|
||||
ProducerStage: "normalize",
|
||||
OutputKind: TranscriptOutputKindFinal,
|
||||
},
|
||||
{
|
||||
SourceID: ArtifactTranscriptFinalTrimmed,
|
||||
CanonicalRelPath: TranscriptPathFinalTrimmed,
|
||||
ProducerStage: "trim",
|
||||
OutputKind: TranscriptOutputKindFinalTrimmed,
|
||||
},
|
||||
}
|
||||
|
||||
got := PlannedTranscriptArtifacts()
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("PlannedTranscriptArtifacts() = %#v, want %#v", got, want)
|
||||
}
|
||||
|
||||
for _, spec := range want {
|
||||
gotSpec, ok := LookupPlannedTranscriptArtifact(spec.SourceID)
|
||||
if !ok {
|
||||
t.Fatalf("LookupPlannedTranscriptArtifact(%q) ok = false, want true", spec.SourceID)
|
||||
}
|
||||
if gotSpec != spec {
|
||||
t.Fatalf("LookupPlannedTranscriptArtifact(%q) = %#v, want %#v", spec.SourceID, gotSpec, spec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranscriptArtifactSlicesAreCopies(t *testing.T) {
|
||||
runtime := RuntimeTranscriptArtifacts()
|
||||
runtime[0].SourceID = "changed"
|
||||
if got := RuntimeTranscriptArtifacts()[0].SourceID; got != ArtifactTranscriptBase {
|
||||
t.Fatalf("RuntimeTranscriptArtifacts()[0].SourceID = %q, want %q", got, ArtifactTranscriptBase)
|
||||
}
|
||||
|
||||
planned := PlannedTranscriptArtifacts()
|
||||
planned[0].SourceID = "changed"
|
||||
if got := PlannedTranscriptArtifacts()[0].SourceID; got != ArtifactTranscriptBase {
|
||||
t.Fatalf("PlannedTranscriptArtifacts()[0].SourceID = %q, want %q", got, ArtifactTranscriptBase)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuntimeArtifactRegistryUsesTranscriptSpecs(t *testing.T) {
|
||||
for _, transcript := range RuntimeTranscriptArtifacts() {
|
||||
spec, ok := artifactRegistry[transcript.SourceID]
|
||||
if !ok {
|
||||
t.Fatalf("artifactRegistry missing %q", transcript.SourceID)
|
||||
}
|
||||
if spec.CanonicalRelPath != transcript.CanonicalRelPath ||
|
||||
spec.ProducerStage != transcript.ProducerStage ||
|
||||
spec.OutputKind != transcript.OutputKind ||
|
||||
spec.ContentKind != contentTranscriptJSON {
|
||||
t.Fatalf("artifactRegistry[%q] = %#v, want transcript spec %#v", transcript.SourceID, spec, transcript)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user