Establish prepared metadata identity
This commit is contained in:
@@ -20,6 +20,7 @@ type preparedReport struct {
|
||||
resolved report.Resolved
|
||||
reportFacts ReportFacts
|
||||
moduleSnapshot module.Snapshot
|
||||
identity briefing.PreparedIdentity
|
||||
briefingMetadata briefing.Metadata
|
||||
sourceWarnings []weatherdata.SourceWarning
|
||||
dataPackage []byte
|
||||
@@ -54,11 +55,13 @@ func prepareReport(req prepareReportRequest) (preparedReport, error) {
|
||||
if err != nil {
|
||||
return preparedReport{}, &preparationError{operation: "build report facts", err: err}
|
||||
}
|
||||
buildContext := briefingBuildContext(req.Config, req.Resolved, reportFacts.Collected)
|
||||
identity := briefing.BuildPreparedIdentity(buildContext)
|
||||
moduleSnapshot, err := BuildModuleSnapshotFromFacts(ModuleSnapshotRequest{Config: req.Config, Resolved: req.Resolved}, reportFacts)
|
||||
if err != nil {
|
||||
return preparedReport{}, &preparationError{operation: "build module snapshot", err: err}
|
||||
}
|
||||
metadata := briefing.BuildMetadata(briefingBuildContext(req.Config, req.Resolved, reportFacts.Collected))
|
||||
metadata := briefing.BuildMetadata(identity, buildContext.Bundle)
|
||||
dataPackage, err := promptinput.Build(promptinput.BuildRequest{Metadata: promptMetadata(metadata), Modules: moduleSnapshot})
|
||||
if err != nil {
|
||||
return preparedReport{}, &preparationError{operation: "build data package", err: err}
|
||||
@@ -80,6 +83,10 @@ func prepareReport(req prepareReportRequest) (preparedReport, error) {
|
||||
if err != nil {
|
||||
return preparedReport{}, &preparationError{operation: "copy prepared module snapshot", err: err}
|
||||
}
|
||||
clonedIdentity, err := clonePreparedValue(identity)
|
||||
if err != nil {
|
||||
return preparedReport{}, &preparationError{operation: "copy prepared identity", err: err}
|
||||
}
|
||||
clonedMetadata, err := clonePreparedValue(metadata)
|
||||
if err != nil {
|
||||
return preparedReport{}, &preparationError{operation: "copy prepared briefing metadata", err: err}
|
||||
@@ -88,8 +95,9 @@ func prepareReport(req prepareReportRequest) (preparedReport, error) {
|
||||
resolved: cloneResolved(req.Resolved),
|
||||
reportFacts: clonedFacts,
|
||||
moduleSnapshot: clonedSnapshot,
|
||||
identity: clonedIdentity,
|
||||
briefingMetadata: clonedMetadata,
|
||||
sourceWarnings: append([]weatherdata.SourceWarning(nil), clonedMetadata.SourceWarnings...),
|
||||
sourceWarnings: append([]weatherdata.SourceWarning(nil), clonedIdentity.SourceWarnings...),
|
||||
dataPackage: append([]byte(nil), serializedDataPackage...),
|
||||
handler: handler,
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/briefing"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/collect"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/module"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/weatherdata"
|
||||
)
|
||||
|
||||
@@ -29,11 +31,12 @@ func TestPrepareReportBuildsImmutableDeterministicInputs(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("second prepareReport() error = %v", err)
|
||||
}
|
||||
if len(prepared.dataPackage) == 0 || !bytes.Equal(prepared.dataPackage, repeated.dataPackage) || !reflect.DeepEqual(prepared.briefingMetadata, repeated.briefingMetadata) {
|
||||
t.Fatalf("prepared package/metadata are not deterministic: %q/%#v", prepared.dataPackage, prepared.briefingMetadata)
|
||||
if len(prepared.dataPackage) == 0 || !bytes.Equal(prepared.dataPackage, repeated.dataPackage) || !reflect.DeepEqual(prepared.identity, repeated.identity) || !reflect.DeepEqual(prepared.briefingMetadata, repeated.briefingMetadata) {
|
||||
t.Fatalf("prepared package/identity/metadata are not deterministic: %q/%#v/%#v", prepared.dataPackage, prepared.identity, prepared.briefingMetadata)
|
||||
}
|
||||
|
||||
originalDataPackage := append([]byte(nil), prepared.dataPackage...)
|
||||
originalIdentity := prepared.identity
|
||||
originalMetadata := prepared.briefingMetadata
|
||||
originalWarnings := append([]weatherdata.SourceWarning(nil), prepared.sourceWarnings...)
|
||||
metadata, snapshot, reportFacts, err := prepared.renderInputs()
|
||||
@@ -52,7 +55,7 @@ func TestPrepareReportBuildsImmutableDeterministicInputs(t *testing.T) {
|
||||
bundle.Sources[0].Query["mutated"] = "true"
|
||||
}
|
||||
|
||||
if !bytes.Equal(prepared.dataPackage, originalDataPackage) || !reflect.DeepEqual(prepared.briefingMetadata, originalMetadata) || !reflect.DeepEqual(prepared.sourceWarnings, originalWarnings) {
|
||||
if !bytes.Equal(prepared.dataPackage, originalDataPackage) || !reflect.DeepEqual(prepared.identity, originalIdentity) || !reflect.DeepEqual(prepared.briefingMetadata, originalMetadata) || !reflect.DeepEqual(prepared.sourceWarnings, originalWarnings) {
|
||||
t.Fatalf("prepared values changed after caller mutation: %#v", prepared)
|
||||
}
|
||||
if prepared.reportFacts.Collected.Hourly.Periods[0].TextDescription == "mutated after preparation" {
|
||||
@@ -62,3 +65,47 @@ func TestPrepareReportBuildsImmutableDeterministicInputs(t *testing.T) {
|
||||
t.Fatal("prepared report values retain consumer mutation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareReportSharesOneMetadataIdentity(t *testing.T) {
|
||||
cfg := generationConfig()
|
||||
bundle := generationBundle(t)
|
||||
resolved, err := ResolveGenerate(GenerateRequest{
|
||||
Config: cfg, Report: ReportDaily,
|
||||
Date: generationTime("2026-05-29T12:00:00-05:00"), Now: generationTime("2026-05-29T08:30:00-05:00"),
|
||||
}, generationTime("2026-05-29T08:30:00-05:00"))
|
||||
if err != nil {
|
||||
t.Fatalf("ResolveGenerate() error = %v", err)
|
||||
}
|
||||
prepared, err := prepareReport(prepareReportRequest{Config: cfg, Resolved: resolved, Collection: collect.Result{Bundle: &bundle}})
|
||||
if err != nil {
|
||||
t.Fatalf("prepareReport() error = %v", err)
|
||||
}
|
||||
|
||||
identity := prepared.identity
|
||||
metadata := prepared.briefingMetadata
|
||||
if metadata.RunID != identity.RunID || metadata.ReportID != identity.ReportID || metadata.Variant != identity.Variant || metadata.PromptID != identity.PromptID || !metadata.GeneratedAt.Equal(identity.GeneratedAt) || metadata.Units != identity.Units || metadata.Timezone != identity.Timezone || metadata.ValidPeriod != identity.ValidPeriod || !reflect.DeepEqual(metadata.Location, identity.Location) || !reflect.DeepEqual(metadata.SourceWarnings, identity.SourceWarnings) {
|
||||
t.Fatalf("briefing metadata does not match prepared identity: %#v/%#v", metadata, identity)
|
||||
}
|
||||
|
||||
prompt := promptMetadata(metadata)
|
||||
if prompt.RunID != identity.RunID || prompt.ReportID != identity.ReportID || prompt.Variant != identity.Variant || prompt.PromptID != identity.PromptID || !prompt.GeneratedAt.Equal(identity.GeneratedAt) || prompt.Timezone != identity.Timezone || prompt.ValidPeriod != identity.ValidPeriod || !reflect.DeepEqual(prompt.SourceWarnings, identity.SourceWarnings) {
|
||||
t.Fatalf("prompt metadata does not match prepared identity: %#v/%#v", prompt, identity)
|
||||
}
|
||||
|
||||
moduleMetadata, found, err := module.StanzaValue[briefing.MetadataModule](prepared.moduleSnapshot, "metadata")
|
||||
if err != nil || !found {
|
||||
t.Fatalf("metadata stanza = %#v/%t/%v", moduleMetadata, found, err)
|
||||
}
|
||||
if moduleMetadata.RunID != identity.RunID || moduleMetadata.ReportID != identity.ReportID || moduleMetadata.Variant != identity.Variant || moduleMetadata.PromptID != identity.PromptID || !moduleMetadata.GeneratedAt.Equal(identity.GeneratedAt) || moduleMetadata.Units != identity.Units || moduleMetadata.Timezone != identity.Timezone || moduleMetadata.ValidPeriod != identity.ValidPeriod || !reflect.DeepEqual(moduleMetadata.Location, identity.Location) {
|
||||
t.Fatalf("module metadata does not match prepared identity: %#v/%#v", moduleMetadata, identity)
|
||||
}
|
||||
if len(moduleMetadata.SourceWarnings) != len(identity.SourceWarnings) {
|
||||
t.Fatalf("module source warnings = %#v, want %#v", moduleMetadata.SourceWarnings, identity.SourceWarnings)
|
||||
}
|
||||
for index, warning := range identity.SourceWarnings {
|
||||
summary := moduleMetadata.SourceWarnings[index]
|
||||
if summary.Source != warning.Source || summary.Code != warning.Code || summary.Severity != warning.Severity || summary.Message != warning.Message || summary.CompletenessImpact != warning.CompletenessImpact {
|
||||
t.Fatalf("module source warning %d = %#v, want %#v", index, summary, warning)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user