Updated transcript artifact names and canonical paths to use a consistent, role-based nomenclature

This commit is contained in:
2026-05-22 19:05:23 -05:00
parent e920f3a8d5
commit cee52aa092
56 changed files with 991 additions and 463 deletions

View File

@@ -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.

View File

@@ -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)
}

View File

@@ -215,10 +215,10 @@ func (c *ArtifactCatalog) addEntry(entry CatalogEntry) error {
func runtimeBuiltInArtifactIDs() []string {
return []string{
ArtifactTranscriptMerged,
ArtifactTranscriptBase,
ArtifactTranscriptPolished,
ArtifactTranscriptFull,
ArtifactTranscriptTrimmed,
ArtifactTranscriptFinal,
ArtifactTranscriptFinalTrimmed,
ArtifactBoundsSession,
}
}

View File

@@ -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)
}
}

View File

@@ -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},
},
},
},

View File

@@ -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)
}

View 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)
}

View 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)
}
}
}