Require manifest-backed local artifacts

This commit is contained in:
2026-09-02 14:09:32 +00:00
parent 62b1f9b0ae
commit 57719b7d81
28 changed files with 392 additions and 179 deletions

View File

@@ -43,3 +43,15 @@ func setCurrentAnalyzeEvidence(t *testing.T, m *manifest.Manifest, key, relative
OutputSize: info.Size(), ProducerRunID: "run-1", UpdatedAt: now,
}
}
func setCurrentBuiltInEvidence(t *testing.T, m *manifest.Manifest, stage, kind, sourceID, absolutePath string) {
t.Helper()
checksum, err := artifacts.SHA256File(absolutePath)
if err != nil {
t.Fatal(err)
}
m.MarkStageSucceeded(stage, time.Date(2026, 5, 16, 1, 2, 3, 0, time.UTC), []manifest.ArtifactRecord{{
Kind: kind, SourceID: sourceID, LocalPath: absolutePath,
ProducerRunID: stage + "-run", Checksum: checksum,
}})
}

View File

@@ -29,6 +29,7 @@ func TestResolveAnalyzeInputIdentitiesSupportsEverySourceKind(t *testing.T) {
setup: func(t *testing.T, env *Env, m *manifest.Manifest) string {
path := filepath.Join(sessionPathsForEnv(env, m.SessionID).TranscriptsDir, "final.trimmed.json")
writeAnalyzeFile(t, path, `{"segments":[]}`)
setCurrentBuiltInEvidence(t, m, "trim", artifacts.TranscriptOutputKindFinalTrimmed, artifacts.ArtifactTranscriptFinalTrimmed, path)
return path
},
wantOutputKind: "transcript_final_trimmed",
@@ -47,6 +48,7 @@ func TestResolveAnalyzeInputIdentitiesSupportsEverySourceKind(t *testing.T) {
setup: func(t *testing.T, env *Env, m *manifest.Manifest) string {
paths := sessionPathsForEnv(env, m.SessionID)
writeAnalyzeFile(t, filepath.Join(paths.TranscriptsDir, "final.trimmed.json"), `{"segments":[]}`)
setCurrentBuiltInEvidence(t, m, "trim", artifacts.TranscriptOutputKindFinalTrimmed, artifacts.ArtifactTranscriptFinalTrimmed, filepath.Join(paths.TranscriptsDir, "final.trimmed.json"))
return configureAnalyzeExtractionFixture(t, env, m)["encounters"]
},
wantOutputKind: "notarius_lane",
@@ -177,6 +179,9 @@ func TestResolveAnalyzeInputIdentitiesRejectsUnsafePathsAndFileTypes(t *testing.
setup: func(t *testing.T, env *Env, m *manifest.Manifest) {
t.Helper()
path := filepath.Join(sessionPathsForEnv(env, m.SessionID).TranscriptsDir, "final.trimmed.json")
if err := os.Remove(path); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(path, 0o755); err != nil {
t.Fatal(err)
}
@@ -213,6 +218,7 @@ func TestAnalyzeInputIdentityIsStableAcrossWorkspaceRelocationAndChangesWithByte
env, m, _ := setupAnalyzeEnv(t)
path := filepath.Join(sessionPathsForEnv(env, m.SessionID).TranscriptsDir, "final.trimmed.json")
writeAnalyzeFile(t, path, body)
setCurrentBuiltInEvidence(t, m, "trim", artifacts.TranscriptOutputKindFinalTrimmed, artifacts.ArtifactTranscriptFinalTrimmed, path)
resolved, err := resolveAnalyzeInputIdentities(map[string]config.ScriptoriumInputConfig{
"transcript": {Source: "narratio.transcript.final_trimmed", Required: true},
}, newAnalyzeIdentityExecution(t, env, m))

View File

@@ -151,7 +151,6 @@ func TestPlanAnalyzeWorkSchedulesEveryNonCurrentReason(t *testing.T) {
analyzeReconciliationStale,
analyzeReconciliationMissing,
analyzeReconciliationFailed,
analyzeReconciliationLegacy,
analyzeReconciliationNonResumable,
}
for _, reason := range reasons {
@@ -245,12 +244,12 @@ func TestPlanAnalyzeWorkProjectionPreservesOnlyValidConfiguredAuthority(t *testi
}
}
func TestPlanAnalyzeWorkLegacyPartialSelectionDoesNotPromoteUnselectedOutput(t *testing.T) {
func TestPlanAnalyzeWorkNonResumablePartialSelectionDoesNotPromoteUnselectedOutput(t *testing.T) {
configured := map[string]config.ScriptoriumArtifactConfig{
"target": {Enabled: true}, "legacy_unselected": {Enabled: true},
}
reconciliation := analyzePlanningReconciliation(configured, map[string]analyzeReconciliationReason{
"target": analyzeReconciliationLegacy, "legacy_unselected": analyzeReconciliationLegacy,
"target": analyzeReconciliationNonResumable, "legacy_unselected": analyzeReconciliationNonResumable,
}, nil)
plan, err := planAnalyzeWork(&config.ScriptoriumConfig{Artifacts: configured}, []string{"target"}, false, reconciliation)
if err != nil {
@@ -258,7 +257,7 @@ func TestPlanAnalyzeWorkLegacyPartialSelectionDoesNotPromoteUnselectedOutput(t *
}
assertAnalyzePlanKeys(t, "execution", plan.ExecutionOrder, []string{"target"})
if len(plan.ProjectedRecords) != 0 {
t.Fatalf("legacy projection = %#v, want no current records", plan.ProjectedRecords)
t.Fatalf("non-resumable projection = %#v, want no current records", plan.ProjectedRecords)
}
}

View File

@@ -18,7 +18,6 @@ const (
analyzeReconciliationStale analyzeReconciliationReason = "stale"
analyzeReconciliationMissing analyzeReconciliationReason = "missing"
analyzeReconciliationFailed analyzeReconciliationReason = "failed"
analyzeReconciliationLegacy analyzeReconciliationReason = "legacy"
analyzeReconciliationRemoved analyzeReconciliationReason = "removed"
analyzeReconciliationNonResumable analyzeReconciliationReason = "non_resumable"
)
@@ -109,8 +108,8 @@ func reconcileAnalyzeArtifact(
return result
}
if stageRecord.AnalyzeStateVersion == 0 {
result.Reason = analyzeReconciliationLegacy
result.Detail = "analyze manifest uses aggregate-only legacy state"
result.Reason = analyzeReconciliationNonResumable
result.Detail = "analyze manifest state contract is absent"
return result
}
if stageRecord.AnalyzeStateVersion != manifest.AnalyzeStateContractVersion {

View File

@@ -32,7 +32,7 @@ func TestReconcileAnalyzeArtifactsClassifiesStoredState(t *testing.T) {
},
},
{
name: "legacy record", want: analyzeReconciliationLegacy,
name: "missing state contract", want: analyzeReconciliationNonResumable,
mutate: func(_ *testing.T, _ *Env, m *manifest.Manifest) {
m.Stages["analyze"].AnalyzeStateVersion = 0
m.Stages["analyze"].AnalyzeArtifacts = nil

View File

@@ -264,6 +264,7 @@ func TestAnalyzeOmitsOptionalCanonicalPreviousRecapWhenUnavailable(t *testing.T)
env, m, fake := setupAnalyzeEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeAnalyzeFile(t, filepath.Join(paths.TranscriptsDir, "polished.json"), `{"segments":[]}`)
setCurrentBuiltInEvidence(t, m, "polish", artifacts.TranscriptOutputKindPolished, artifacts.ArtifactTranscriptPolished, filepath.Join(paths.TranscriptsDir, "polished.json"))
env.Config.Pipeline.Scriptorium.Artifacts["session_recap"] = config.ScriptoriumArtifactConfig{
Enabled: true,
@@ -303,6 +304,7 @@ func TestAnalyzeUsesRunLocalPathsAndMaterializesCanonical(t *testing.T) {
m.RunID = "20260518T010203Z-abcdef12"
paths := sessionPathsForEnv(env, m.SessionID)
writeAnalyzeFile(t, filepath.Join(paths.TranscriptsDir, "final.trimmed.json"), `{"segments":[]}`)
setCurrentBuiltInEvidence(t, m, "trim", artifacts.TranscriptOutputKindFinalTrimmed, artifacts.ArtifactTranscriptFinalTrimmed, filepath.Join(paths.TranscriptsDir, "final.trimmed.json"))
result, err := (analyzeStage{}).Run(context.Background(), env, m)
if err != nil {
@@ -386,6 +388,7 @@ func TestAnalyzeIncludesCanonicalPreviousRecapWhenPreparedCacheExists(t *testing
env, m, fake := setupAnalyzeEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeAnalyzeFile(t, filepath.Join(paths.TranscriptsDir, "polished.json"), `{"segments":[]}`)
setCurrentBuiltInEvidence(t, m, "polish", artifacts.TranscriptOutputKindPolished, artifacts.ArtifactTranscriptPolished, filepath.Join(paths.TranscriptsDir, "polished.json"))
previousRecapPath := mustPreviousArtifactPath(t, paths, "artifacts/session_recap.md")
writeAnalyzeFile(t, previousRecapPath, "previous recap\n")
@@ -938,6 +941,7 @@ func TestAnalyzeFailsWhenOutputPathMissing(t *testing.T) {
func TestAnalyzeFailsWhenTrimmedTranscriptMissing(t *testing.T) {
env, m, _ := setupAnalyzeEnv(t)
delete(m.Stages, "trim")
_, err := (analyzeStage{}).Run(context.Background(), env, m)
if err == nil {
t.Fatal("expected error, got nil")
@@ -964,6 +968,9 @@ func TestAnalyzeMissingOptionalBuiltInInputsAreOmitted(t *testing.T) {
"optional": {Source: source, Required: false},
}
env.Config.Pipeline.Scriptorium.Artifacts["session_recap"] = artifact
if source == artifacts.ArtifactTranscriptFinalTrimmed {
delete(m.Stages, "trim")
}
if _, err := (analyzeStage{}).Run(context.Background(), env, m); err != nil {
t.Fatalf("Run() error = %v", err)
@@ -1000,6 +1007,9 @@ func TestAnalyzeMissingRequiredBuiltInInputsProvideExecutableGuidance(t *testing
"required": {Source: tt.source, Required: true},
}
env.Config.Pipeline.Scriptorium.Artifacts["session_recap"] = artifact
if tt.source == artifacts.ArtifactTranscriptFinalTrimmed {
delete(m.Stages, "trim")
}
_, err := (analyzeStage{}).Run(context.Background(), env, m)
if err == nil {
@@ -1020,6 +1030,7 @@ func TestAnalyzeSupportsProcessedTranscriptSourceWhenConfigured(t *testing.T) {
env, m, fake := setupAnalyzeEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeAnalyzeFile(t, filepath.Join(paths.TranscriptsDir, "polished.json"), `{"segments":[]}`)
setCurrentBuiltInEvidence(t, m, "polish", artifacts.TranscriptOutputKindPolished, artifacts.ArtifactTranscriptPolished, filepath.Join(paths.TranscriptsDir, "polished.json"))
artifact := env.Config.Pipeline.Scriptorium.Artifacts["session_recap"]
artifact.Inputs["transcript"] = config.ScriptoriumInputConfig{
@@ -1069,6 +1080,7 @@ func TestAnalyzeSupportsNormalizedTranscriptSourceWhenConfigured(t *testing.T) {
paths := sessionPathsForEnv(env, m.SessionID)
normalizedPath := filepath.Join(paths.TranscriptsDir, "final.json")
writeAnalyzeFile(t, normalizedPath, `{"segments":[{"id":1}]}`)
setCurrentBuiltInEvidence(t, m, "normalize", artifacts.TranscriptOutputKindFinal, artifacts.ArtifactTranscriptFinal, normalizedPath)
artifact := env.Config.Pipeline.Scriptorium.Artifacts["session_recap"]
artifact.Inputs["transcript"] = config.ScriptoriumInputConfig{
@@ -1094,6 +1106,7 @@ func TestAnalyzeSupportsRenderedMarkdownTranscriptSourceWhenConfigured(t *testin
paths := sessionPathsForEnv(env, m.SessionID)
markdownPath := filepath.Join(paths.TranscriptsDir, "final.md")
writeAnalyzeFile(t, markdownPath, "# Session Transcript\n")
setCurrentBuiltInEvidence(t, m, "render", artifacts.TranscriptOutputKindFinalMarkdown, artifacts.ArtifactTranscriptFinalMarkdown, markdownPath)
artifact := env.Config.Pipeline.Scriptorium.Artifacts["session_recap"]
artifact.Inputs["transcript"] = config.ScriptoriumInputConfig{
@@ -1243,9 +1256,7 @@ func TestAnalyzeSupportsCanonicalNormalizedTranscriptSourceFromManifestOutput(t
manifestPath := filepath.Join(paths.ArtifactsDir, "normalized.from-manifest.json")
writeAnalyzeFile(t, fallbackPath, `{"segments":[{"id":999}]}`)
writeAnalyzeFile(t, manifestPath, `{"segments":[{"id":10}]}`)
m.MarkStageSucceeded("normalize", time.Now().UTC(), []manifest.ArtifactRecord{
{Kind: "transcript_final", LocalPath: manifestPath},
})
setCurrentBuiltInEvidence(t, m, "normalize", artifacts.TranscriptOutputKindFinal, artifacts.ArtifactTranscriptFinal, manifestPath)
artifact := env.Config.Pipeline.Scriptorium.Artifacts["session_recap"]
artifact.Inputs["transcript"] = config.ScriptoriumInputConfig{
@@ -1273,9 +1284,7 @@ func TestAnalyzeSupportsNormalizedTranscriptSourceFromManifestOutput(t *testing.
manifestPath := filepath.Join(paths.ArtifactsDir, "normalized.from-manifest.json")
writeAnalyzeFile(t, fallbackPath, `{"segments":[{"id":999}]}`)
writeAnalyzeFile(t, manifestPath, `{"segments":[{"id":10}]}`)
m.MarkStageSucceeded("normalize", time.Now().UTC(), []manifest.ArtifactRecord{
{Kind: "transcript_final", LocalPath: manifestPath},
})
setCurrentBuiltInEvidence(t, m, "normalize", artifacts.TranscriptOutputKindFinal, artifacts.ArtifactTranscriptFinal, manifestPath)
artifact := env.Config.Pipeline.Scriptorium.Artifacts["session_recap"]
artifact.Inputs["transcript"] = config.ScriptoriumInputConfig{
@@ -1689,6 +1698,17 @@ func setupAnalyzeEnv(t *testing.T) (*Env, *manifest.Manifest, *scriptorium.FakeR
Scriptorium: fake,
}
m := manifest.New("2026-05-03", time.Now().UTC())
paths := store.SessionPathsFor("icewind-dale", "2026-05-03")
trimmedPath := filepath.Join(paths.TranscriptsDir, "final.trimmed.json")
writeAnalyzeFile(t, trimmedPath, `{"segments":[]}`)
checksum, err := artifacts.SHA256File(trimmedPath)
if err != nil {
t.Fatalf("SHA256File(%q): %v", trimmedPath, err)
}
m.MarkStageSucceeded("trim", time.Now().UTC(), []manifest.ArtifactRecord{{
Kind: artifacts.TranscriptOutputKindFinalTrimmed, SourceID: artifacts.ArtifactTranscriptFinalTrimmed,
LocalPath: trimmedPath, ProducerRunID: "trim-run", Checksum: checksum,
}})
return env, m, fake
}

View File

@@ -30,7 +30,7 @@ func (extractStage) ValidateResume(_ context.Context, env *Env, m *manifest.Mani
paths := sessionPathsForEnv(env, sessionID)
input, err := artifacts.ResolveExtractionInputIdentity(paths, m)
if err != nil {
return ResumeValidation{}, fmt.Errorf("extract resume: resolve final-trimmed transcript identity: %w", err)
return NonResumable("final-trimmed transcript evidence is unavailable or changed"), nil
}
references, err := resolveExtractReferences(paths, m, cfg, sessionID)
if err != nil {

View File

@@ -1012,9 +1012,13 @@ func setupExtractEnv(t *testing.T) (*Env, *manifest.Manifest, *notarius.FakeRunn
m := manifest.New(sessionID, time.Now().UTC())
m.Campaign = campaign
m.RunID = runID
inputChecksum, err := artifacts.SHA256File(inputPath)
if err != nil {
t.Fatalf("SHA256File(input) error = %v", err)
}
m.MarkStageSucceeded("trim", time.Now().UTC(), []manifest.ArtifactRecord{{
Kind: artifactmodel.TranscriptOutputKindFinalTrimmed, SourceID: artifactmodel.SourceTranscriptFinalTrimmed,
LocalPath: inputPath,
LocalPath: inputPath, ProducerRunID: "trim-run", Checksum: inputChecksum,
}})
outputRoot := artifacts.SessionRunNotariusOutputRootForCampaign(workspace, campaign, sessionID, runID)

View File

@@ -20,10 +20,7 @@ func TestNormalizeStageConsumesProcessedTranscriptFromManifest(t *testing.T) {
manifestProcessed := filepath.Join(paths.ArtifactsDir, "processed.from-manifest.json")
writeFile(t, manifestProcessed, `{"segments":[{"id":10}]}`)
writeFile(t, filepath.Join(paths.TranscriptsDir, "polished.json"), `{"segments":[{"id":99}]}`)
m.MarkStageSucceeded("polish", time.Now().UTC(), []manifest.ArtifactRecord{{
Kind: "transcript_polished",
LocalPath: manifestProcessed,
}})
setCurrentBuiltInEvidence(t, m, "polish", artifacts.TranscriptOutputKindPolished, artifacts.ArtifactTranscriptPolished, manifestProcessed)
result, err := (normalizeStage{}).Run(context.Background(), env, m)
if err != nil {
@@ -41,21 +38,18 @@ func TestNormalizeStageConsumesProcessedTranscriptFromManifest(t *testing.T) {
}
}
func TestNormalizeStageFallsBackToProcessedTranscriptPath(t *testing.T) {
func TestNormalizeStageRejectsIncidentalProcessedTranscriptPath(t *testing.T) {
env, m, ser := setupNormalizeEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
fallback := filepath.Join(paths.TranscriptsDir, "polished.json")
writeFile(t, fallback, `{"segments":[{"id":1}]}`)
_, err := (normalizeStage{}).Run(context.Background(), env, m)
if err != nil {
t.Fatalf("normalize.Run() error = %v", err)
if err == nil || !strings.Contains(err.Error(), "processed transcript input is required") {
t.Fatalf("normalize.Run() error = %v, want unavailable manifest input", err)
}
if len(ser.NormalizeRequests) != 1 {
t.Fatalf("normalize requests = %d, want 1", len(ser.NormalizeRequests))
}
if ser.NormalizeRequests[0].InputTranscriptPath != fallback {
t.Fatalf("input transcript = %q, want %q", ser.NormalizeRequests[0].InputTranscriptPath, fallback)
if len(ser.NormalizeRequests) != 0 {
t.Fatalf("normalize requests = %d, want 0", len(ser.NormalizeRequests))
}
}
@@ -74,6 +68,7 @@ func TestNormalizeStageFailsWhenProcessedTranscriptInvalidJSON(t *testing.T) {
env, m, _ := setupNormalizeEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "polished.json"), "not-json")
setCurrentBuiltInEvidence(t, m, "polish", artifacts.TranscriptOutputKindPolished, artifacts.ArtifactTranscriptPolished, filepath.Join(paths.TranscriptsDir, "polished.json"))
_, err := (normalizeStage{}).Run(context.Background(), env, m)
if err == nil {
@@ -88,6 +83,7 @@ func TestNormalizeStageFailsWhenProcessedTranscriptMissingSegments(t *testing.T)
env, m, _ := setupNormalizeEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "polished.json"), `{"schema":"audita.processed.v1"}`)
setCurrentBuiltInEvidence(t, m, "polish", artifacts.TranscriptOutputKindPolished, artifacts.ArtifactTranscriptPolished, filepath.Join(paths.TranscriptsDir, "polished.json"))
_, err := (normalizeStage{}).Run(context.Background(), env, m)
if err == nil {
@@ -102,6 +98,7 @@ func TestNormalizeStagePassesConfiguredOutputSchemaToAdapter(t *testing.T) {
env, m, ser := setupNormalizeEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "polished.json"), `{"segments":[{"id":1}]}`)
setCurrentBuiltInEvidence(t, m, "polish", artifacts.TranscriptOutputKindPolished, artifacts.ArtifactTranscriptPolished, filepath.Join(paths.TranscriptsDir, "polished.json"))
env.Config.Pipeline.Normalize.OutputSchema = "seriatim-full"
result, err := (normalizeStage{}).Run(context.Background(), env, m)
@@ -123,6 +120,7 @@ func TestNormalizeStageRecordsNormalizedTranscriptOutputKind(t *testing.T) {
env, m, _ := setupNormalizeEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "polished.json"), `{"segments":[{"id":1}]}`)
setCurrentBuiltInEvidence(t, m, "polish", artifacts.TranscriptOutputKindPolished, artifacts.ArtifactTranscriptPolished, filepath.Join(paths.TranscriptsDir, "polished.json"))
result, err := (normalizeStage{}).Run(context.Background(), env, m)
if err != nil {
@@ -140,6 +138,7 @@ func TestNormalizeStageRecordsReportLogAndGeneratedConfigRefs(t *testing.T) {
env, m, _ := setupNormalizeEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "polished.json"), `{"segments":[{"id":1}]}`)
setCurrentBuiltInEvidence(t, m, "polish", artifacts.TranscriptOutputKindPolished, artifacts.ArtifactTranscriptPolished, filepath.Join(paths.TranscriptsDir, "polished.json"))
report := true
env.Config.Pipeline.Normalize.Report = &report
@@ -166,6 +165,7 @@ func TestNormalizeStageFailsWhenAdapterReturnsError(t *testing.T) {
env, m, ser := setupNormalizeEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "polished.json"), `{"segments":[{"id":1}]}`)
setCurrentBuiltInEvidence(t, m, "polish", artifacts.TranscriptOutputKindPolished, artifacts.ArtifactTranscriptPolished, filepath.Join(paths.TranscriptsDir, "polished.json"))
ser.NormalizeErr = errors.New("normalize failed")
_, err := (normalizeStage{}).Run(context.Background(), env, m)
@@ -181,6 +181,7 @@ func TestNormalizeStageFailsWhenNormalizedOutputInvalid(t *testing.T) {
env, m, ser := setupNormalizeEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "polished.json"), `{"segments":[{"id":1}]}`)
setCurrentBuiltInEvidence(t, m, "polish", artifacts.TranscriptOutputKindPolished, artifacts.ArtifactTranscriptPolished, filepath.Join(paths.TranscriptsDir, "polished.json"))
badOutput := filepath.Join(paths.TranscriptsDir, "normalized.bad.json")
writeFile(t, badOutput, "not-json")
ser.NormalizeResult = seriatim.NormalizeResult{OutputNormalizedPath: badOutput}
@@ -198,6 +199,7 @@ func TestNormalizeStageReportEnabledFailsWhenReportMissing(t *testing.T) {
env, m, ser := setupNormalizeEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "polished.json"), `{"segments":[{"id":1}]}`)
setCurrentBuiltInEvidence(t, m, "polish", artifacts.TranscriptOutputKindPolished, artifacts.ArtifactTranscriptPolished, filepath.Join(paths.TranscriptsDir, "polished.json"))
report := true
env.Config.Pipeline.Normalize.Report = &report
ser.NormalizeResult = seriatim.NormalizeResult{ReportPath: filepath.Join(paths.ArtifactsDir, "missing-report.json")}
@@ -218,6 +220,7 @@ func TestNormalizeStageUsesRunLocalPathsAndMaterializesCanonical(t *testing.T) {
m.RunID = "20260518T010203Z-abcdef12"
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "polished.json"), `{"segments":[{"id":1}]}`)
setCurrentBuiltInEvidence(t, m, "polish", artifacts.TranscriptOutputKindPolished, artifacts.ArtifactTranscriptPolished, filepath.Join(paths.TranscriptsDir, "polished.json"))
result, err := (normalizeStage{}).Run(context.Background(), env, m)
if err != nil {

View File

@@ -21,9 +21,7 @@ func TestPolishStagePolishesMergedTranscriptAndRecordsMetadata(t *testing.T) {
mergedPath := filepath.Join(paths.TranscriptsDir, "base.json")
writeFile(t, mergedPath, `{"segments":[]}`)
writeFile(t, filepath.Join(paths.InputsDir, "glossary.yml"), "terms: []\n")
m.MarkStageSucceeded("merge", time.Now().UTC(), []manifest.ArtifactRecord{
{Kind: "transcript_base", LocalPath: mergedPath},
})
setCurrentBuiltInEvidence(t, m, "merge", artifacts.TranscriptOutputKindBase, artifacts.ArtifactTranscriptBase, mergedPath)
fake := &audita.FakeRunner{}
env.Audita = fake
@@ -84,7 +82,7 @@ func TestPolishStagePolishesMergedTranscriptAndRecordsMetadata(t *testing.T) {
}
}
func TestPolishStageFallsBackToMergedTranscriptPath(t *testing.T) {
func TestPolishStageRejectsIncidentalMergedTranscriptPath(t *testing.T) {
env, m := setupPolishEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
mergedPath := filepath.Join(paths.TranscriptsDir, "base.json")
@@ -95,14 +93,11 @@ func TestPolishStageFallsBackToMergedTranscriptPath(t *testing.T) {
env.Audita = fake
_, err := (polishStage{}).Run(context.Background(), env, m)
if err != nil {
t.Fatalf("polish.Run() error = %v", err)
if err == nil || !strings.Contains(err.Error(), "merged transcript input is required") {
t.Fatalf("polish.Run() error = %v, want unavailable manifest input", err)
}
if len(fake.Requests) != 1 {
t.Fatalf("requests = %d, want 1", len(fake.Requests))
}
if fake.Requests[0].MergedTranscriptPath != mergedPath {
t.Fatalf("merged path = %q, want %q", fake.Requests[0].MergedTranscriptPath, mergedPath)
if len(fake.Requests) != 0 {
t.Fatalf("requests = %d, want 0", len(fake.Requests))
}
}
@@ -125,6 +120,7 @@ func TestPolishStageFailsWhenMergedTranscriptInvalidJSON(t *testing.T) {
env, m := setupPolishEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "base.json"), "not-json")
setCurrentBuiltInEvidence(t, m, "merge", artifacts.TranscriptOutputKindBase, artifacts.ArtifactTranscriptBase, filepath.Join(paths.TranscriptsDir, "base.json"))
writeFile(t, filepath.Join(paths.InputsDir, "glossary.yml"), "terms: []\n")
env.Audita = &audita.FakeRunner{}
@@ -141,6 +137,7 @@ func TestPolishStageFailsWhenGlossaryMissing(t *testing.T) {
env, m := setupPolishEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "base.json"), `{"segments":[]}`)
setCurrentBuiltInEvidence(t, m, "merge", artifacts.TranscriptOutputKindBase, artifacts.ArtifactTranscriptBase, filepath.Join(paths.TranscriptsDir, "base.json"))
env.Audita = &audita.FakeRunner{}
_, err := (polishStage{}).Run(context.Background(), env, m)
@@ -156,6 +153,7 @@ func TestPolishStageFailsWhenAdapterFails(t *testing.T) {
env, m := setupPolishEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "base.json"), `{"segments":[]}`)
setCurrentBuiltInEvidence(t, m, "merge", artifacts.TranscriptOutputKindBase, artifacts.ArtifactTranscriptBase, filepath.Join(paths.TranscriptsDir, "base.json"))
writeFile(t, filepath.Join(paths.InputsDir, "glossary.yml"), "terms: []\n")
env.Audita = &audita.FakeRunner{Err: errors.New("audita failed")}
@@ -172,6 +170,7 @@ func TestPolishStageFailsWhenProcessedOutputInvalid(t *testing.T) {
env, m := setupPolishEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "base.json"), `{"segments":[]}`)
setCurrentBuiltInEvidence(t, m, "merge", artifacts.TranscriptOutputKindBase, artifacts.ArtifactTranscriptBase, filepath.Join(paths.TranscriptsDir, "base.json"))
writeFile(t, filepath.Join(paths.InputsDir, "glossary.yml"), "terms: []\n")
badPath := filepath.Join(paths.TranscriptsDir, "processed.invalid.json")
writeFile(t, badPath, `{"schema":"audita.processed.v1","segments":"wrong-type"}`)
@@ -190,6 +189,7 @@ func TestPolishStageFailsWhenReportInvalid(t *testing.T) {
env, m := setupPolishEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "base.json"), `{"segments":[]}`)
setCurrentBuiltInEvidence(t, m, "merge", artifacts.TranscriptOutputKindBase, artifacts.ArtifactTranscriptBase, filepath.Join(paths.TranscriptsDir, "base.json"))
writeFile(t, filepath.Join(paths.InputsDir, "glossary.yml"), "terms: []\n")
badReport := filepath.Join(paths.ArtifactsDir, "bad.report.json")
writeFile(t, badReport, "not-json")
@@ -211,6 +211,7 @@ func TestPolishStageUsesRunLocalPathsAndMaterializesCanonical(t *testing.T) {
m.RunID = "20260518T010203Z-abcdef12"
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "base.json"), `{"segments":[]}`)
setCurrentBuiltInEvidence(t, m, "merge", artifacts.TranscriptOutputKindBase, artifacts.ArtifactTranscriptBase, filepath.Join(paths.TranscriptsDir, "base.json"))
writeFile(t, filepath.Join(paths.InputsDir, "glossary.yml"), "terms: []\n")
fake := &audita.FakeRunner{}

View File

@@ -1071,6 +1071,7 @@ func publishFixture(t *testing.T) (*Env, *manifest.Manifest, string) {
for _, name := range publishPrerequisiteStages {
m.MarkStageSucceeded(name, time.Date(2026, 5, 16, 1, 2, 3, 0, time.UTC), nil)
}
setCurrentBuiltInEvidence(t, m, "trim", artifacts.TranscriptOutputKindFinalTrimmed, artifacts.ArtifactTranscriptFinalTrimmed, filepath.Join(sessionRoot, "transcripts", "final.trimmed.json"))
setCurrentAnalyzeEvidence(t, m, "session_recap", "artifacts/session_recap.md", filepath.Join(sessionRoot, "artifacts", "session_recap.md"))
env := &Env{

View File

@@ -44,8 +44,8 @@ func TestRenderStageResolvesManifestInputsFirst(t *testing.T) {
writeFile(t, filepath.Join(paths.TranscriptsDir, "final.json"), `{"segments":[{"id":1}]}`)
writeFile(t, filepath.Join(paths.TranscriptsDir, "final.trimmed.json"), `{"segments":[{"id":2}]}`)
m.MarkStageSucceeded("normalize", time.Now().UTC(), []manifest.ArtifactRecord{{Kind: "transcript_final", LocalPath: manifestFinal}})
m.MarkStageSucceeded("trim", time.Now().UTC(), []manifest.ArtifactRecord{{Kind: "transcript_final_trimmed", LocalPath: manifestTrimmed}})
setCurrentBuiltInEvidence(t, m, "normalize", artifacts.TranscriptOutputKindFinal, artifacts.ArtifactTranscriptFinal, manifestFinal)
setCurrentBuiltInEvidence(t, m, "trim", artifacts.TranscriptOutputKindFinalTrimmed, artifacts.ArtifactTranscriptFinalTrimmed, manifestTrimmed)
result, err := (renderStage{}).Run(context.Background(), env, m)
if err != nil {
@@ -82,6 +82,8 @@ func TestRenderStageTitleFallsBackToSessionTitle(t *testing.T) {
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "final.json"), `{"segments":[{"id":1}]}`)
writeFile(t, filepath.Join(paths.TranscriptsDir, "final.trimmed.json"), `{"segments":[{"id":2}]}`)
setCurrentBuiltInEvidence(t, m, "normalize", artifacts.TranscriptOutputKindFinal, artifacts.ArtifactTranscriptFinal, filepath.Join(paths.TranscriptsDir, "final.json"))
setCurrentBuiltInEvidence(t, m, "trim", artifacts.TranscriptOutputKindFinalTrimmed, artifacts.ArtifactTranscriptFinalTrimmed, filepath.Join(paths.TranscriptsDir, "final.trimmed.json"))
env.Config.Pipeline.Render.Title = ""
env.Config.Session.Title = "Session Title"
@@ -112,6 +114,7 @@ func TestRenderStageFailsWhenFinalTrimmedInputMissing(t *testing.T) {
env, m, _ := setupRenderEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "final.json"), `{"segments":[{"id":1}]}`)
setCurrentBuiltInEvidence(t, m, "normalize", artifacts.TranscriptOutputKindFinal, artifacts.ArtifactTranscriptFinal, filepath.Join(paths.TranscriptsDir, "final.json"))
_, err := (renderStage{}).Run(context.Background(), env, m)
if err == nil {
@@ -130,6 +133,8 @@ func TestRenderStageUsesRunLocalOutputsAndMaterializesCanonical(t *testing.T) {
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "final.json"), `{"segments":[{"id":1}]}`)
writeFile(t, filepath.Join(paths.TranscriptsDir, "final.trimmed.json"), `{"segments":[{"id":2}]}`)
setCurrentBuiltInEvidence(t, m, "normalize", artifacts.TranscriptOutputKindFinal, artifacts.ArtifactTranscriptFinal, filepath.Join(paths.TranscriptsDir, "final.json"))
setCurrentBuiltInEvidence(t, m, "trim", artifacts.TranscriptOutputKindFinalTrimmed, artifacts.ArtifactTranscriptFinalTrimmed, filepath.Join(paths.TranscriptsDir, "final.trimmed.json"))
result, err := (renderStage{}).Run(context.Background(), env, m)
if err != nil {

View File

@@ -21,9 +21,7 @@ func TestTrimStageConsumesNormalizedAndProducesTrimmedTranscript(t *testing.T) {
paths := sessionPathsForEnv(env, m.SessionID)
normalized := filepath.Join(paths.TranscriptsDir, "final.json")
writeFile(t, normalized, `{"segments":[{"id":10},{"id":868}]}`)
m.MarkStageSucceeded("normalize", time.Now().UTC(), []manifest.ArtifactRecord{
{Kind: "transcript_final", LocalPath: normalized},
})
setCurrentBuiltInEvidence(t, m, "normalize", artifacts.TranscriptOutputKindFinal, artifacts.ArtifactTranscriptFinal, normalized)
scr.BoundsBody = `{"confidence":"high","trim_action":"trim","start_segment_id":10,"end_segment_id":868,"warnings":[]}`
result, err := (trimStage{}).Run(context.Background(), env, m)
@@ -76,6 +74,7 @@ func TestTrimStageUsesConfiguredScriptoriumInputName(t *testing.T) {
paths := sessionPathsForEnv(env, m.SessionID)
normalized := filepath.Join(paths.TranscriptsDir, "final.json")
writeFile(t, normalized, `{"segments":[{"id":10},{"id":11}]}`)
setCurrentBuiltInEvidence(t, m, "normalize", artifacts.TranscriptOutputKindFinal, artifacts.ArtifactTranscriptFinal, normalized)
scr.BoundsBody = `{"trim_action":"trim","start_segment_id":10,"end_segment_id":11}`
cfg := env.Config.Pipeline.Trim
@@ -99,6 +98,7 @@ func TestTrimStageRecordsLogAndGeneratedConfigRefs(t *testing.T) {
env, m, scr, _ := setupTrimEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "final.json"), `{"segments":[{"id":1},{"id":2}]}`)
setCurrentBuiltInEvidence(t, m, "normalize", artifacts.TranscriptOutputKindFinal, artifacts.ArtifactTranscriptFinal, filepath.Join(paths.TranscriptsDir, "final.json"))
scr.BoundsBody = `{"trim_action":"trim","start_segment_id":1,"end_segment_id":2}`
result, err := (trimStage{}).Run(context.Background(), env, m)
@@ -119,6 +119,7 @@ func TestTrimStageRenderDebugDiagnosticsAreNotStageOutputs(t *testing.T) {
env, m, scr, _ := setupTrimEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "final.json"), `{"segments":[{"id":1},{"id":2}]}`)
setCurrentBuiltInEvidence(t, m, "normalize", artifacts.TranscriptOutputKindFinal, artifacts.ArtifactTranscriptFinal, filepath.Join(paths.TranscriptsDir, "final.json"))
scr.BoundsBody = `{"trim_action":"trim","start_segment_id":1,"end_segment_id":2}`
cfg := *env.Config.Pipeline.Trim
@@ -183,6 +184,7 @@ func TestTrimStageFailsWhenNormalizedTranscriptInvalidJSON(t *testing.T) {
env, m, _, _ := setupTrimEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "final.json"), "not-json")
setCurrentBuiltInEvidence(t, m, "normalize", artifacts.TranscriptOutputKindFinal, artifacts.ArtifactTranscriptFinal, filepath.Join(paths.TranscriptsDir, "final.json"))
_, err := (trimStage{}).Run(context.Background(), env, m)
if err == nil {
t.Fatal("expected error, got nil")
@@ -196,6 +198,7 @@ func TestTrimStageFailsWhenNormalizedTranscriptMissingSegmentsArray(t *testing.T
env, m, _, _ := setupTrimEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "final.json"), `{"schema":"audita.processed.v1"}`)
setCurrentBuiltInEvidence(t, m, "normalize", artifacts.TranscriptOutputKindFinal, artifacts.ArtifactTranscriptFinal, filepath.Join(paths.TranscriptsDir, "final.json"))
_, err := (trimStage{}).Run(context.Background(), env, m)
if err == nil {
t.Fatal("expected error, got nil")
@@ -209,6 +212,7 @@ func TestTrimStageFailsWhenBoundsOutputInvalidJSON(t *testing.T) {
env, m, scr, _ := setupTrimEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "final.json"), `{"segments":[{"id":1},{"id":2}]}`)
setCurrentBuiltInEvidence(t, m, "normalize", artifacts.TranscriptOutputKindFinal, artifacts.ArtifactTranscriptFinal, filepath.Join(paths.TranscriptsDir, "final.json"))
scr.BoundsBody = "not-json"
_, err := (trimStage{}).Run(context.Background(), env, m)
if err == nil {
@@ -223,6 +227,7 @@ func TestTrimStageFailsWhenBoundsRangeIsDescending(t *testing.T) {
env, m, scr, _ := setupTrimEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "final.json"), `{"segments":[{"id":1},{"id":2}]}`)
setCurrentBuiltInEvidence(t, m, "normalize", artifacts.TranscriptOutputKindFinal, artifacts.ArtifactTranscriptFinal, filepath.Join(paths.TranscriptsDir, "final.json"))
scr.BoundsBody = `{"trim_action":"trim","start_segment_id":2,"end_segment_id":1}`
_, err := (trimStage{}).Run(context.Background(), env, m)
if err == nil {
@@ -237,6 +242,7 @@ func TestTrimStageFailsWhenBoundsIDsMissingFromTranscript(t *testing.T) {
env, m, scr, _ := setupTrimEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "final.json"), `{"segments":[{"id":20},{"id":21}]}`)
setCurrentBuiltInEvidence(t, m, "normalize", artifacts.TranscriptOutputKindFinal, artifacts.ArtifactTranscriptFinal, filepath.Join(paths.TranscriptsDir, "final.json"))
scr.BoundsBody = `{"trim_action":"trim","start_segment_id":10,"end_segment_id":21}`
_, err := (trimStage{}).Run(context.Background(), env, m)
if err == nil {
@@ -251,6 +257,7 @@ func TestTrimStageFailsWhenScriptoriumAdapterFails(t *testing.T) {
env, m, scr, _ := setupTrimEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "final.json"), `{"segments":[{"id":1},{"id":2}]}`)
setCurrentBuiltInEvidence(t, m, "normalize", artifacts.TranscriptOutputKindFinal, artifacts.ArtifactTranscriptFinal, filepath.Join(paths.TranscriptsDir, "final.json"))
scr.RunErr = errors.New("bounds failed")
_, err := (trimStage{}).Run(context.Background(), env, m)
if err == nil {
@@ -265,6 +272,7 @@ func TestTrimStageFailsWhenSeriatimTrimAdapterFails(t *testing.T) {
env, m, scr, ser := setupTrimEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeFile(t, filepath.Join(paths.TranscriptsDir, "final.json"), `{"segments":[{"id":1},{"id":2}]}`)
setCurrentBuiltInEvidence(t, m, "normalize", artifacts.TranscriptOutputKindFinal, artifacts.ArtifactTranscriptFinal, filepath.Join(paths.TranscriptsDir, "final.json"))
scr.BoundsBody = `{"trim_action":"trim","start_segment_id":1,"end_segment_id":2}`
ser.TrimErr = errors.New("trim failed")
_, err := (trimStage{}).Run(context.Background(), env, m)
@@ -282,6 +290,7 @@ func TestTrimStageDisabledCopiesNormalizedTranscript(t *testing.T) {
normalized := filepath.Join(paths.TranscriptsDir, "final.json")
normalizedBody := `{"segments":[{"id":1,"text":"alpha"},{"id":2,"text":"beta"}]}`
writeFile(t, normalized, normalizedBody)
setCurrentBuiltInEvidence(t, m, "normalize", artifacts.TranscriptOutputKindFinal, artifacts.ArtifactTranscriptFinal, normalized)
disabled := *env.Config.Pipeline.Trim
enabled := false
@@ -316,6 +325,7 @@ func TestTrimStageUsesRunLocalPathsAndMaterializesCanonical(t *testing.T) {
paths := sessionPathsForEnv(env, m.SessionID)
normalized := filepath.Join(paths.TranscriptsDir, "final.json")
writeFile(t, normalized, `{"segments":[{"id":10},{"id":11}]}`)
setCurrentBuiltInEvidence(t, m, "normalize", artifacts.TranscriptOutputKindFinal, artifacts.ArtifactTranscriptFinal, normalized)
scr.BoundsBody = `{"trim_action":"trim","start_segment_id":10,"end_segment_id":11,"warnings":[]}`
result, err := (trimStage{}).Run(context.Background(), env, m)