From c2d3b410f2c5862820fc76f367cae7fdfb044381 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 2 Aug 2026 16:33:32 +0200 Subject: [PATCH] Fixed stored briefings failing their integrity check after the enum serialization change --- .../VisualBriefing/VisualBriefingJson.cs | 63 +++++++++++++------ 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingJson.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingJson.cs index 143d1360..1db7191f 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingJson.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingJson.cs @@ -5,40 +5,67 @@ using System.Text.Json.Serialization; namespace AIStudio.Assistants.VisualBriefing; /// -/// Provides the single JSON configuration used by visual briefing persistence and hashing. +/// Provides the two JSON configurations used by visual briefing hashing and persistence. /// +/// +/// The split is deliberate and the two halves must not be merged back together. Hashing needs bytes +/// that never change, persistence wants output that stays readable as the app evolves. One shared +/// configuration cannot serve both: improving the readability of stored files would rewrite the very +/// bytes that older briefings were hashed with, and every one of them would fail its integrity check. +/// internal static class VisualBriefingJson { /// /// Gets compact canonical JSON options. /// - internal static JsonSerializerOptions Compact { get; } = Create(writeIndented: false); + /// + /// Treat these options as frozen. Their exact bytes are hashed into stored briefings: the artifact + /// header is serialized into the briefing document, and reading that document back re-serializes the + /// header to recompute the document hash. Every build stage likewise hashes its serialized output to + /// decide what a later build may reuse. Any change here — a converter, a naming policy, an encoder — + /// therefore invalidates every briefing that was ever written, which surfaces as a failed integrity + /// check rather than as a build error. This is why enums stay numeric here even though the persisted + /// manifest writes their member names. + /// + internal static JsonSerializerOptions Compact { get; } = Create(writeIndented: false, enumsAsText: false); /// /// Gets indented persistence JSON options. /// - internal static JsonSerializerOptions Indented { get; } = Create(writeIndented: true); + /// + /// These options are free to evolve, because nothing hashes their output. They write the briefing + /// manifest and the diagnostics clipboard text, where readable enum names are worth having. + /// + internal static JsonSerializerOptions Indented { get; } = Create(writeIndented: true, enumsAsText: true); /// /// Creates the shared JSON configuration. /// /// - /// Enums are written as their member names instead of numbers. Stored briefings outlive many - /// releases, so a numeric value would silently change meaning as soon as somebody inserts or - /// reorders an enum member. Most visual briefing enums carry the converter as an attribute - /// already; this option covers the ones defined outside the feature, such as the target language - /// and the audience enums. Reading still accepts numbers, so briefings written before this - /// change keep loading. + /// Wherever the output is not hashed, enums are written as their member names instead of numbers: + /// stored briefings outlive many releases, so a numeric value would silently change meaning as soon + /// as somebody inserts or reorders an enum member. Most visual briefing enums carry the converter as + /// an attribute already, which applies to both configurations; this option only covers the ones + /// defined outside the feature, such as the target language and the audience enums. Reading accepts + /// numbers as well, so manifests written before this distinction existed keep loading. /// /// Whether serialized JSON should be indented. + /// Whether enums without their own converter are written as member names. /// The configured serializer options. - private static JsonSerializerOptions Create(bool writeIndented) => new() + private static JsonSerializerOptions Create(bool writeIndented, bool enumsAsText) { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = false, - WriteIndented = writeIndented, - Encoder = JavaScriptEncoder.Default, - UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow, - Converters = { new JsonStringEnumConverter() }, - }; -} + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = false, + WriteIndented = writeIndented, + Encoder = JavaScriptEncoder.Default, + UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow, + }; + + if (enumsAsText) + options.Converters.Add(new JsonStringEnumConverter()); + + return options; + } +} \ No newline at end of file