diff --git a/internal/app/app_test.go b/internal/app/app_test.go index e4e25ed..b571358 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -358,6 +358,7 @@ func TestGenerateReportIncludesRecentChangesFromPriorSnapshot(t *testing.T) { } _, err = store.SaveMetadata(context.Background(), state.BuildMetadata(priorResolved, priorBriefing, state.ArtifactPaths{ Briefing: priorBriefingPath, + Metadata: priorPaths.Metadata, DataPackage: priorPaths.DataPackage, Preflight: priorPaths.Preflight, RenderedReport: priorPaths.RenderedReport, @@ -472,6 +473,7 @@ func TestTomorrowReportCanCompareAgainstPriorDailySnapshot(t *testing.T) { } _, err = store.SaveMetadata(context.Background(), state.BuildMetadata(priorResolved, priorBriefing, state.ArtifactPaths{ Briefing: priorBriefingPath, + Metadata: priorPaths.Metadata, DataPackage: priorPaths.DataPackage, Preflight: priorPaths.Preflight, RenderedReport: priorPaths.RenderedReport, @@ -538,6 +540,7 @@ func TestGenerateThreeDayReportWritesReportAndRecentChanges(t *testing.T) { } _, err = store.SaveMetadata(context.Background(), state.BuildMetadata(priorResolved, priorBriefing, state.ArtifactPaths{ Briefing: priorBriefingPath, + Metadata: priorPaths.Metadata, DataPackage: priorPaths.DataPackage, Preflight: priorPaths.Preflight, RenderedReport: priorPaths.RenderedReport, @@ -613,6 +616,7 @@ func TestGenerateWeekendReportWritesReportAndRecentChanges(t *testing.T) { } _, err = store.SaveMetadata(context.Background(), state.BuildMetadata(priorResolved, priorBriefing, state.ArtifactPaths{ Briefing: priorBriefingPath, + Metadata: priorPaths.Metadata, DataPackage: priorPaths.DataPackage, Preflight: priorPaths.Preflight, RenderedReport: priorPaths.RenderedReport, diff --git a/internal/report/definition.go b/internal/report/definition.go index 6d43289..79171c8 100644 --- a/internal/report/definition.go +++ b/internal/report/definition.go @@ -38,7 +38,6 @@ type Definition struct { Name string PromptID string ComparisonStrategy ComparisonStrategy - DefaultOutputName string ArtifactGroup string BatchOutputName string Generated bool diff --git a/internal/report/registry.go b/internal/report/registry.go index d0bb0e3..acb56af 100644 --- a/internal/report/registry.go +++ b/internal/report/registry.go @@ -13,7 +13,6 @@ func DefaultRegistry() Registry { Name: "Daily Report", PromptID: "weather.daily_report", ComparisonStrategy: CompareSameValidDate, - DefaultOutputName: "daily.md", ArtifactGroup: "daily", BatchOutputName: "daily.md", Generated: true, @@ -26,7 +25,6 @@ func DefaultRegistry() Registry { Name: "Tomorrow Planning Brief", PromptID: "weather.daily_report", ComparisonStrategy: CompareSameValidDate, - DefaultOutputName: "tomorrow.md", ArtifactGroup: "daily", BatchOutputName: "tomorrow.md", Generated: true, @@ -39,7 +37,6 @@ func DefaultRegistry() Registry { Name: "3-Day Outlook", PromptID: "weather.three_day_outlook", ComparisonStrategy: CompareSameValidDate, - DefaultOutputName: "three_day.md", ArtifactGroup: "three-day", BatchOutputName: "three-day.md", Generated: true, @@ -52,7 +49,6 @@ func DefaultRegistry() Registry { Name: "Weekend Outlook", PromptID: "weather.weekend_outlook", ComparisonStrategy: CompareWeekendWindow, - DefaultOutputName: "weekend.md", ArtifactGroup: "weekend", BatchOutputName: "weekend.md", Generated: true, @@ -65,7 +61,6 @@ func DefaultRegistry() Registry { Name: "Storm Report", PromptID: "weather.storm_report", ComparisonStrategy: CompareExplicitWindow, - DefaultOutputName: "storm.md", ArtifactGroup: "storm", BatchOutputName: "storm.md", Generated: true, diff --git a/internal/state/filesystem.go b/internal/state/filesystem.go index eca86cb..9fa909a 100644 --- a/internal/state/filesystem.go +++ b/internal/state/filesystem.go @@ -154,14 +154,13 @@ func (s *FilesystemStore) SaveMetadata(_ context.Context, metadata Metadata) (st if metadata.PreflightPath == "" { return "", fmt.Errorf("metadata preflight path is required") } - path := metadataPathFromStored(metadata) - if path == "" { - return "", fmt.Errorf("metadata path cannot be resolved") + if metadata.MetadataPath == "" { + return "", fmt.Errorf("metadata path is required") } - if err := fileutil.WriteJSONAtomic(path, metadata); err != nil { + if err := fileutil.WriteJSONAtomic(metadata.MetadataPath, metadata); err != nil { return "", err } - return path, nil + return metadata.MetadataPath, nil } func (s *FilesystemStore) FindPriorSnapshot(_ context.Context, resolved report.Resolved) (*PriorSnapshot, error) { @@ -366,14 +365,6 @@ func readJSON(path string, target any) error { return nil } -func metadataPathFromStored(metadata Metadata) string { - if metadata.BriefingPath == "" { - return "" - } - filename := metadata.RunID + ".metadata.json" - return filepath.Join(filepath.Dir(metadata.BriefingPath), filename) -} - func sameValidDate(metadata Metadata, resolved report.Resolved) bool { return metadata.ValidPeriod.Start.Format("2006-01-02") == resolved.ValidPeriod.Start.Format("2006-01-02") } diff --git a/internal/state/filesystem_test.go b/internal/state/filesystem_test.go index 77dd399..e9b5d06 100644 --- a/internal/state/filesystem_test.go +++ b/internal/state/filesystem_test.go @@ -122,6 +122,43 @@ func TestSaveArtifactsAndMetadataRoundTrip(t *testing.T) { if decoded.RenderedReportPath != renderedReportPath { t.Fatalf("RenderedReportPath = %q, want %q", decoded.RenderedReportPath, renderedReportPath) } + if strings.Contains(string(data), "MetadataPath") || strings.Contains(string(data), "metadataPath") { + t.Fatalf("metadata JSON includes runtime-only MetadataPath:\n%s", string(data)) + } +} + +func TestSaveMetadataUsesExplicitMetadataPath(t *testing.T) { + store := newTestStore(t) + resolved := resolveDailyAt(t, "2026-05-29T05:00:00-05:00") + briefingPackage := stateBriefingPackage(resolved) + paths, err := store.Paths(resolved) + if err != nil { + t.Fatalf("Paths() error = %v", err) + } + otherDir := filepath.Join(t.TempDir(), "other-artifacts") + otherBriefingPath := filepath.Join(otherDir, resolved.Metadata().RunID+".briefing.json") + derivedMetadataPath := filepath.Join(otherDir, resolved.Metadata().RunID+".metadata.json") + + metadata := BuildMetadata(resolved, briefingPackage, ArtifactPaths{ + Briefing: otherBriefingPath, + Metadata: paths.Metadata, + DataPackage: paths.DataPackage, + Preflight: paths.Preflight, + RenderedReport: paths.RenderedReport, + }) + metadataPath, err := store.SaveMetadata(context.Background(), metadata) + if err != nil { + t.Fatalf("SaveMetadata() error = %v", err) + } + if metadataPath != paths.Metadata { + t.Fatalf("SaveMetadata() path = %q, want explicit metadata path %q", metadataPath, paths.Metadata) + } + if _, err := os.Stat(paths.Metadata); err != nil { + t.Fatalf("expected explicit metadata path %q: %v", paths.Metadata, err) + } + if _, err := os.Stat(derivedMetadataPath); !os.IsNotExist(err) { + t.Fatalf("derived metadata path stat error = %v, want not exist", err) + } } func TestFindPriorSnapshot(t *testing.T) { diff --git a/internal/state/metadata.go b/internal/state/metadata.go index d35109a..6bb99f6 100644 --- a/internal/state/metadata.go +++ b/internal/state/metadata.go @@ -14,6 +14,7 @@ const MetadataSchemaVersion = "weatherreporter.metadata.v1" type Metadata struct { SchemaVersion string `json:"schemaVersion"` RunID string `json:"runId"` + MetadataPath string `json:"-"` ReportID report.ID `json:"reportId"` Variant string `json:"variant,omitempty"` PromptID string `json:"promptId"` @@ -35,6 +36,7 @@ func BuildMetadata(resolved report.Resolved, briefingPackage briefing.Package, p return Metadata{ SchemaVersion: MetadataSchemaVersion, RunID: metadata.RunID, + MetadataPath: paths.Metadata, ReportID: metadata.ReportID, Variant: briefingPackage.Metadata.Variant, PromptID: metadata.PromptID,