package semanticreconcile import ( "testing" "gitea.maximumdirect.net/eric/notarius/internal/framework/llm" "gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline" ) func TestEngineMetadataAndFingerprintsCoverCoreIdentity(t *testing.T) { base := newTestEngine(t, &recordingReconciliationClient{}, DefaultLimits()) metadata := base.ManifestMetadata() for _, key := range []string{ "prompt_id", "prompt_version", "prompt_sha256", "response_schema_key", "response_schema_id", "response_schema_name", "response_schema_version", "response_schema_sha256", "semantic_reconciliation_policy", "semantic_reconciliation_limits", } { if metadata[key] == nil || metadata[key] == "" { t.Fatalf("metadata[%q] = %#v, want populated core identity", key, metadata[key]) } } limits, ok := metadata["semantic_reconciliation_limits"].(map[string]any) if !ok || len(limits) != 3 || limits["context_radius"] == nil || limits["maximum_candidates"] == nil || limits["maximum_material_bytes"] == nil { t.Fatalf("limit metadata = %#v, want complete limits", metadata["semantic_reconciliation_limits"]) } fingerprints := base.CheckpointFingerprints() wantNames := []string{"prompt", "response_schema", "semantic_reconciliation_policy", "semantic_reconciliation_limits"} if len(fingerprints) != len(wantNames) { t.Fatalf("fingerprints = %#v, want required categories", fingerprints) } for index, want := range wantNames { if fingerprints[index].Name != want || fingerprints[index].Value == "" { t.Fatalf("fingerprint %d = %#v, want %q with value", index, fingerprints[index], want) } } metadata["prompt_id"] = "changed" limits["context_radius"] = -1 fingerprints[0].Name = "changed" if got := base.ManifestMetadata(); got["prompt_id"] == "changed" || got["semantic_reconciliation_limits"].(map[string]any)["context_radius"] == -1 { t.Fatalf("ManifestMetadata() exposed retained state: %#v", got) } if got := base.CheckpointFingerprints(); got[0].Name == "changed" { t.Fatalf("CheckpointFingerprints() exposed retained state: %#v", got) } } func TestCoreFingerprintsChangeWithBehavioralIdentity(t *testing.T) { base := newTestEngine(t, &recordingReconciliationClient{}, DefaultLimits()) baseFingerprints := base.CheckpointFingerprints() promptChanged, err := NewEngine(&recordingReconciliationClient{}, testPromptSpec("b"), DefaultLimits()) if err != nil { t.Fatal(err) } assertOnlyFingerprintChanged(t, baseFingerprints, promptChanged.CheckpointFingerprints(), "prompt") schema, err := LoadResponseSchema() if err != nil { t.Fatal(err) } schema.SHA256 = testDigest("b") schemaChanged := newEngine(&recordingReconciliationClient{}, base.prompt, schema, DefaultLimits()) assertOnlyFingerprintChanged(t, baseFingerprints, schemaChanged.CheckpointFingerprints(), "response_schema") limits := DefaultLimits() limits.ContextRadius++ limitsChanged, err := NewEngine(&recordingReconciliationClient{}, base.prompt, limits) if err != nil { t.Fatal(err) } assertOnlyFingerprintChanged(t, baseFingerprints, limitsChanged.CheckpointFingerprints(), "semantic_reconciliation_limits") } func TestInvalidEngineIdentityHasNoMetadataOrFingerprints(t *testing.T) { invalid := newEngine(&recordingReconciliationClient{}, testPromptSpec("a"), llm.ResponseSchema{}, DefaultLimits()) if invalid.ManifestMetadata() != nil || invalid.CheckpointFingerprints() != nil { t.Fatalf("invalid engine exposed identity: metadata %#v fingerprints %#v", invalid.ManifestMetadata(), invalid.CheckpointFingerprints()) } } func assertOnlyFingerprintChanged(t *testing.T, before, after []pipeline.CheckpointFingerprint, changedName string) { t.Helper() if len(before) != len(after) { t.Fatalf("fingerprint counts differ: %#v %#v", before, after) } for index := range before { changed := before[index] != after[index] if changed != (before[index].Name == changedName) { t.Fatalf("fingerprint %q change = %t, want only %q changed\nbefore: %#v\nafter: %#v", before[index].Name, changed, changedName, before, after) } } }