Renamed VisualBriefingJson options

This commit is contained in:
Thorsten Sommer 2026-08-02 16:53:14 +02:00
parent c2d3b410f2
commit ebb9a6f135
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
19 changed files with 79 additions and 79 deletions

View File

@ -30,9 +30,9 @@ public sealed partial class VisualBriefingArtifactService
private const string DATA_ELEMENT_ID = "mwai-briefing-data"; private const string DATA_ELEMENT_ID = "mwai-briefing-data";
/// <summary> /// <summary>
/// Gets the shared compact JSON configuration. /// Gets the frozen JSON configuration whose bytes the document hash covers.
/// </summary> /// </summary>
private static readonly JsonSerializerOptions JSON_OPTIONS = VisualBriefingJson.Compact; private static readonly JsonSerializerOptions JSON_OPTIONS = VisualBriefingJson.Canonical;
/// <summary> /// <summary>
/// Defines <c>HtmlLanguageTagRegex</c> for the visual briefing feature. /// Defines <c>HtmlLanguageTagRegex</c> for the visual briefing feature.

View File

@ -380,5 +380,5 @@ public partial class VisualBriefingAssistant
this.editor.Author, this.editor.Author,
Settings = this.editor.ToSettings(), Settings = this.editor.ToSettings(),
Sources = this.editor.ToSources().Select(source => new { source.Path, source.Kind }).ToArray(), Sources = this.editor.ToSources().Select(source => new { source.Path, source.Kind }).ToArray(),
}, VisualBriefingJson.Compact); }, VisualBriefingJson.Canonical);
} }

View File

@ -135,12 +135,12 @@ internal sealed partial class VisualBriefingBuildOrchestrator
authors = "validation", authors = "validation",
protection = "validation", protection = "validation",
}, },
}, VisualBriefingJson.Compact); }, VisualBriefingJson.Canonical);
VisualBriefingCompilerInvariant.Guard( VisualBriefingCompilerInvariant.Guard(
VisualBriefingBuildStage.COMPILATION, VisualBriefingBuildStage.COMPILATION,
VisualBriefingArtifactService.ValidateGeneratedParts(manifest, VisualBriefingArtifactService.ValidateGeneratedParts(manifest,
JsonSerializer.SerializeToElement(validationDataProperties, VisualBriefingJson.Compact), JsonSerializer.SerializeToElement(validationDataProperties, VisualBriefingJson.Canonical),
compiled.TemplateHtml, compiled.Css, compiled.TemplateHtml, compiled.Css,
content.Charts.Count > 0)); content.Charts.Count > 0));
@ -151,7 +151,7 @@ internal sealed partial class VisualBriefingBuildOrchestrator
ArtifactId = Guid.NewGuid(), ArtifactId = Guid.NewGuid(),
CreatedAtUtc = DateTimeOffset.UtcNow, CreatedAtUtc = DateTimeOffset.UtcNow,
PayloadHash = VisualBriefingHashing.ComputeSections( PayloadHash = VisualBriefingHashing.ComputeSections(
JsonSerializer.Serialize(previousPresentation.Layout, VisualBriefingJson.Compact), JsonSerializer.Serialize(previousPresentation.Layout, VisualBriefingJson.Canonical),
previousPresentation.Profile.ToString(), previousPresentation.Profile.ToString(),
compiled.TemplateHash, compiled.TemplateHash,
compiled.CssHash), compiled.CssHash),

View File

@ -21,8 +21,8 @@ public sealed class VisualBriefingBuildProgressService
public void Publish(VisualBriefingBuildRecord build) public void Publish(VisualBriefingBuildRecord build)
{ {
var snapshot = JsonSerializer.Deserialize<VisualBriefingBuildRecord>( var snapshot = JsonSerializer.Deserialize<VisualBriefingBuildRecord>(
JsonSerializer.Serialize(build, VisualBriefingJson.Compact), JsonSerializer.Serialize(build, VisualBriefingJson.Canonical),
VisualBriefingJson.Compact)!; VisualBriefingJson.Canonical)!;
snapshot.Instruction = string.Empty; snapshot.Instruction = string.Empty;
this.latest[build.BriefingId] = snapshot; this.latest[build.BriefingId] = snapshot;
this.Changed?.Invoke(build.BriefingId); this.Changed?.Invoke(build.BriefingId);

View File

@ -125,7 +125,7 @@ internal static class VisualBriefingChartCompiler
: series, : series,
}; };
return JsonSerializer.SerializeToElement(option, VisualBriefingJson.Compact); return JsonSerializer.SerializeToElement(option, VisualBriefingJson.Canonical);
} }
/// <summary> /// <summary>

View File

@ -65,19 +65,19 @@ internal sealed class VisualBriefingContentStage(StructuredLlmStageRunner stageR
reset = artifact.ResetLabel, reset = artifact.ResetLabel,
brand = "MindWork AI Studio", brand = "MindWork AI Studio",
}, },
}, VisualBriefingJson.Compact); }, VisualBriefingJson.Canonical);
// Keep in sync with the verification in VisualBriefingStore.ReadContentArtifactAsync: // Keep in sync with the verification in VisualBriefingStore.ReadContentArtifactAsync:
artifact.PayloadHash = VisualBriefingHashing.ComputeSections( artifact.PayloadHash = VisualBriefingHashing.ComputeSections(
JsonSerializer.Serialize(artifact.Slots, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.Slots, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(artifact.Charts, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.Charts, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(artifact.Controls, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.Controls, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(artifact.Formulas, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.Formulas, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(artifact.AccessibilityTexts, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.AccessibilityTexts, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(artifact.SourceReferences, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.SourceReferences, VisualBriefingJson.Canonical),
artifact.ResetLabel, artifact.ResetLabel,
JsonSerializer.Serialize(artifact.SourceCoverage, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.SourceCoverage, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(artifact.AssetPlan, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.AssetPlan, VisualBriefingJson.Canonical),
artifact.StructuralSignature); artifact.StructuralSignature);
await store.WriteContentArtifactAsync(manifest.BriefingId, artifact, token); await store.WriteContentArtifactAsync(manifest.BriefingId, artifact, token);
@ -170,13 +170,13 @@ internal sealed class VisualBriefingContentStage(StructuredLlmStageRunner stageR
Target language: {manifest.Settings.TargetLanguage.PromptGeneralPurpose(manifest.Settings.CustomTargetLanguage)} Target language: {manifest.Settings.TargetLanguage.PromptGeneralPurpose(manifest.Settings.CustomTargetLanguage)}
Audience: {manifest.Settings.AudienceProfile}; {manifest.Settings.AudienceAgeGroup}; {manifest.Settings.AudienceOrganizationalLevel}; {manifest.Settings.AudienceExpertise} Audience: {manifest.Settings.AudienceProfile}; {manifest.Settings.AudienceAgeGroup}; {manifest.Settings.AudienceOrganizationalLevel}; {manifest.Settings.AudienceExpertise}
Scope instruction: {manifest.Settings.Instruction} Scope instruction: {manifest.Settings.Instruction}
Exact planned component IDs: {JsonSerializer.Serialize(componentIds, VisualBriefingJson.Compact)} Exact planned component IDs: {JsonSerializer.Serialize(componentIds, VisualBriefingJson.Canonical)}
Exact keys of accessibilityTexts, no others: {JsonSerializer.Serialize(accessibilityTextKeys, VisualBriefingJson.Compact)} Exact keys of accessibilityTexts, no others: {JsonSerializer.Serialize(accessibilityTextKeys, VisualBriefingJson.Canonical)}
Exact required slot IDs with their semantic role and declared type, each to be returned exactly once: {JsonSerializer.Serialize(requiredSlots, VisualBriefingJson.Compact)} Exact required slot IDs with their semantic role and declared type, each to be returned exactly once: {JsonSerializer.Serialize(requiredSlots, VisualBriefingJson.Canonical)}
Exact chart component IDs, each to receive exactly one chart: {JsonSerializer.Serialize(chartComponentIds, VisualBriefingJson.Compact)} Exact chart component IDs, each to receive exactly one chart: {JsonSerializer.Serialize(chartComponentIds, VisualBriefingJson.Canonical)}
Exact control and formula requirements, no controls for any other component: {JsonSerializer.Serialize(controlRequirements, VisualBriefingJson.Compact)} Exact control and formula requirements, no controls for any other component: {JsonSerializer.Serialize(controlRequirements, VisualBriefingJson.Canonical)}
Plan: {JsonSerializer.Serialize(plan.Sections, VisualBriefingJson.Compact)} Plan: {JsonSerializer.Serialize(plan.Sections, VisualBriefingJson.Canonical)}
Evidence: {JsonSerializer.Serialize(new { evidence.Facts, evidence.Metrics, evidence.Tables, evidence.AssetPlan }, VisualBriefingJson.Compact)} Evidence: {JsonSerializer.Serialize(new { evidence.Facts, evidence.Metrics, evidence.Tables, evidence.AssetPlan }, VisualBriefingJson.Canonical)}
"""; """;
} }
@ -257,9 +257,9 @@ internal sealed class VisualBriefingContentStage(StructuredLlmStageRunner stageR
authors = "validation", authors = "validation",
protection = "validation", protection = "validation",
}, },
}, VisualBriefingJson.Compact); }, VisualBriefingJson.Canonical);
var validationData = JsonSerializer.SerializeToElement(data, VisualBriefingJson.Compact); var validationData = JsonSerializer.SerializeToElement(data, VisualBriefingJson.Canonical);
VisualBriefingCompilerInvariant.Guard(VisualBriefingBuildStage.CONTENT, VisualBriefingCompilerInvariant.Guard(VisualBriefingBuildStage.CONTENT,
VisualBriefingArtifactService.ValidateGeneratedParts( VisualBriefingArtifactService.ValidateGeneratedParts(
manifest, manifest,
@ -353,7 +353,7 @@ internal sealed class VisualBriefingContentStage(StructuredLlmStageRunner stageR
ControlId = $"mwai-filter-{index}", ControlId = $"mwai-filter-{index}",
ComponentId = component.ComponentId, ComponentId = component.ComponentId,
Kind = VisualBriefingControlKind.FILTER, Kind = VisualBriefingControlKind.FILTER,
InitialValue = JsonSerializer.SerializeToElement(SHOW_ALL_VALUE, VisualBriefingJson.Compact), InitialValue = JsonSerializer.SerializeToElement(SHOW_ALL_VALUE, VisualBriefingJson.Canonical),
Options = options, Options = options,
}; };
} }

View File

@ -17,7 +17,7 @@ internal static class VisualBriefingData
var dictionary = data.EnumerateObject() var dictionary = data.EnumerateObject()
.Where(property => property.Name is not "_mwai") .Where(property => property.Name is not "_mwai")
.ToDictionary(property => property.Name, property => property.Value.Clone(), StringComparer.Ordinal); .ToDictionary(property => property.Name, property => property.Value.Clone(), StringComparer.Ordinal);
return JsonSerializer.SerializeToElement(dictionary, VisualBriefingJson.Compact); return JsonSerializer.SerializeToElement(dictionary, VisualBriefingJson.Canonical);
} }
/// <summary> /// <summary>

View File

@ -47,11 +47,11 @@ internal sealed class VisualBriefingEvidenceStage(StructuredLlmStageRunner stage
var response = run.Response!; var response = run.Response!;
var payloadHash = VisualBriefingHashing.ComputeSections( var payloadHash = VisualBriefingHashing.ComputeSections(
JsonSerializer.Serialize(response.Facts, VisualBriefingJson.Compact), JsonSerializer.Serialize(response.Facts, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(response.Metrics, VisualBriefingJson.Compact), JsonSerializer.Serialize(response.Metrics, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(response.Tables, VisualBriefingJson.Compact), JsonSerializer.Serialize(response.Tables, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(response.SourceCoverage, VisualBriefingJson.Compact), JsonSerializer.Serialize(response.SourceCoverage, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(response.AssetPlan, VisualBriefingJson.Compact)); JsonSerializer.Serialize(response.AssetPlan, VisualBriefingJson.Canonical));
var artifact = new VisualBriefingEvidenceArtifact var artifact = new VisualBriefingEvidenceArtifact
{ {
@ -124,8 +124,8 @@ internal sealed class VisualBriefingEvidenceStage(StructuredLlmStageRunner stage
return $""" return $"""
Target language: {manifest.Settings.TargetLanguage.PromptGeneralPurpose(manifest.Settings.CustomTargetLanguage)} Target language: {manifest.Settings.TargetLanguage.PromptGeneralPurpose(manifest.Settings.CustomTargetLanguage)}
Scope instruction: {manifest.Settings.Instruction} Scope instruction: {manifest.Settings.Instruction}
Sources, in the same order as the attached files: {JsonSerializer.Serialize(sources, VisualBriefingJson.Compact)} Sources, in the same order as the attached files: {JsonSerializer.Serialize(sources, VisualBriefingJson.Canonical)}
Media transcripts: {JsonSerializer.Serialize(transcripts, VisualBriefingJson.Compact)} Media transcripts: {JsonSerializer.Serialize(transcripts, VisualBriefingJson.Canonical)}
"""; """;
} }

View File

@ -32,7 +32,7 @@ internal static class VisualBriefingInteractionCompiler
controls, controls,
state, state,
formulas = formulaMap, formulas = formulaMap,
}, VisualBriefingJson.Compact); }, VisualBriefingJson.Canonical);
} }
/// <summary> /// <summary>

View File

@ -16,27 +16,27 @@ namespace AIStudio.Assistants.VisualBriefing;
internal static class VisualBriefingJson internal static class VisualBriefingJson
{ {
/// <summary> /// <summary>
/// Gets compact canonical JSON options. /// Gets the frozen options whose byte output is hashed into stored briefings.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Treat these options as frozen. Their exact bytes are hashed into stored briefings: the artifact /// 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 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 /// header to recompute the document hash. Every build stage likewise hashes its serialized output,
/// decide what a later build may reuse. Any change here — a converter, a naming policy, an encoder — /// and a mismatch makes the store discard the stored artifact. Any change here — a converter, a
/// therefore invalidates every briefing that was ever written, which surfaces as a failed integrity /// naming policy, an encoder — therefore invalidates every briefing that was ever written, which
/// check rather than as a build error. This is why enums stay numeric here even though the persisted /// surfaces as a failed integrity check rather than as a build error. This is why enums stay numeric
/// manifest writes their member names. /// here even though the persisted manifest writes their member names.
/// </remarks> /// </remarks>
internal static JsonSerializerOptions Compact { get; } = Create(writeIndented: false, enumsAsText: false); internal static JsonSerializerOptions Canonical { get; } = Create(writeIndented: false, enumsAsText: false);
/// <summary> /// <summary>
/// Gets indented persistence JSON options. /// Gets the options for files that are read back by name rather than by hash.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// These options are free to evolve, because nothing hashes their output. They write the briefing /// 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. /// manifest and the diagnostics clipboard text, where readable enum names are worth having.
/// </remarks> /// </remarks>
internal static JsonSerializerOptions Indented { get; } = Create(writeIndented: true, enumsAsText: true); internal static JsonSerializerOptions Persistence { get; } = Create(writeIndented: true, enumsAsText: true);
/// <summary> /// <summary>
/// Creates the shared JSON configuration. /// Creates the shared JSON configuration.

View File

@ -61,7 +61,7 @@ internal sealed class VisualBriefingLayoutCompiler
reset = content.ResetLabel, reset = content.ResetLabel,
brand = "MindWork AI Studio", brand = "MindWork AI Studio",
}, },
}, VisualBriefingJson.Compact); }, VisualBriefingJson.Canonical);
var html = CompileNode(layout, sections, components, content, true); var html = CompileNode(layout, sections, components, content, true);
var css = CompileCss(profile, layout); var css = CompileCss(profile, layout);

View File

@ -132,5 +132,5 @@ public sealed class VisualBriefingOperationDiagnostics
/// Serializes the diagnostics without user content. /// Serializes the diagnostics without user content.
/// </summary> /// </summary>
/// <returns>A compact JSON document suitable for the clipboard.</returns> /// <returns>A compact JSON document suitable for the clipboard.</returns>
public string ToClipboardText() => JsonSerializer.Serialize(this, VisualBriefingJson.Indented); public string ToClipboardText() => JsonSerializer.Serialize(this, VisualBriefingJson.Persistence);
} }

View File

@ -50,7 +50,7 @@ internal sealed class VisualBriefingPlanStage(StructuredLlmStageRunner stageRunn
await VisualBriefingEvidenceStage.FailAsync(store, build, stage, run, VisualBriefingValidationRule.REFERENCE_INVALID, token); await VisualBriefingEvidenceStage.FailAsync(store, build, stage, run, VisualBriefingValidationRule.REFERENCE_INVALID, token);
var sections = run.Response!.Sections; var sections = run.Response!.Sections;
var payload = JsonSerializer.Serialize(sections, VisualBriefingJson.Compact); var payload = JsonSerializer.Serialize(sections, VisualBriefingJson.Canonical);
var structuralSignature = VisualBriefingHashing.Compute(string.Join('\u001f', sections.Select(section => $"{section.SectionId}:{section.Role}:{section.TitleSlotId}:{section.SummarySlotId}") var structuralSignature = VisualBriefingHashing.Compute(string.Join('\u001f', sections.Select(section => $"{section.SectionId}:{section.Role}:{section.TitleSlotId}:{section.SummarySlotId}")
.Concat(sections.SelectMany(section => section.Components) .Concat(sections.SelectMany(section => section.Components)
@ -113,6 +113,6 @@ internal sealed class VisualBriefingPlanStage(StructuredLlmStageRunner stageRunn
$""" $"""
Audience: {manifest.Settings.AudienceProfile}; {manifest.Settings.AudienceAgeGroup}; {manifest.Settings.AudienceOrganizationalLevel}; {manifest.Settings.AudienceExpertise} Audience: {manifest.Settings.AudienceProfile}; {manifest.Settings.AudienceAgeGroup}; {manifest.Settings.AudienceOrganizationalLevel}; {manifest.Settings.AudienceExpertise}
Scope instruction: {manifest.Settings.Instruction} Scope instruction: {manifest.Settings.Instruction}
Evidence: {JsonSerializer.Serialize(new { evidence.Facts, evidence.Metrics, evidence.Tables, evidence.AssetPlan }, VisualBriefingJson.Compact)} Evidence: {JsonSerializer.Serialize(new { evidence.Facts, evidence.Metrics, evidence.Tables, evidence.AssetPlan }, VisualBriefingJson.Canonical)}
"""; """;
} }

View File

@ -81,7 +81,7 @@ internal sealed class VisualBriefingPresentationStage(StructuredLlmStageRunner s
var compiled = VisualBriefingLayoutCompiler.Compile(plan, content, run.Response.Layout, run.Response.Profile); var compiled = VisualBriefingLayoutCompiler.Compile(plan, content, run.Response.Layout, run.Response.Profile);
var payloadHash = VisualBriefingHashing.ComputeSections( var payloadHash = VisualBriefingHashing.ComputeSections(
JsonSerializer.Serialize(run.Response.Layout, VisualBriefingJson.Compact), JsonSerializer.Serialize(run.Response.Layout, VisualBriefingJson.Canonical),
run.Response.Profile.ToString(), run.Response.Profile.ToString(),
compiled.TemplateHash, compiled.TemplateHash,
compiled.CssHash); compiled.CssHash);
@ -114,7 +114,7 @@ internal sealed class VisualBriefingPresentationStage(StructuredLlmStageRunner s
await store.SaveBuildAsync(build, token); await store.SaveBuildAsync(build, token);
progressService.Publish(build); progressService.Publish(build);
logger.LogInformation("Visual briefing design completed. OperationId={OperationId} BuildId={BuildId} LayoutHash={LayoutHash} TemplateHash={TemplateHash} CssHash={CssHash}", build.OperationId, build.BuildId, VisualBriefingHashing.Compute(JsonSerializer.Serialize(artifact.Layout, VisualBriefingJson.Compact)), artifact.TemplateHash, artifact.CssHash); logger.LogInformation("Visual briefing design completed. OperationId={OperationId} BuildId={BuildId} LayoutHash={LayoutHash} TemplateHash={TemplateHash} CssHash={CssHash}", build.OperationId, build.BuildId, VisualBriefingHashing.Compute(JsonSerializer.Serialize(artifact.Layout, VisualBriefingJson.Canonical)), artifact.TemplateHash, artifact.CssHash);
return artifact; return artifact;
} }
@ -150,9 +150,9 @@ internal sealed class VisualBriefingPresentationStage(StructuredLlmStageRunner s
authors = "validation", authors = "validation",
protection = "validation", protection = "validation",
}, },
}, VisualBriefingJson.Compact); }, VisualBriefingJson.Canonical);
var validationData = JsonSerializer.SerializeToElement(data, VisualBriefingJson.Compact); var validationData = JsonSerializer.SerializeToElement(data, VisualBriefingJson.Canonical);
VisualBriefingCompilerInvariant.Guard(VisualBriefingBuildStage.DESIGN, VisualBriefingCompilerInvariant.Guard(VisualBriefingBuildStage.DESIGN,
VisualBriefingArtifactService.ValidateGeneratedParts(manifest, validationData, compiled.TemplateHtml, compiled.Css, content.Charts.Count > 0)); VisualBriefingArtifactService.ValidateGeneratedParts(manifest, validationData, compiled.TemplateHtml, compiled.Css, content.Charts.Count > 0));
@ -187,12 +187,12 @@ internal sealed class VisualBriefingPresentationStage(StructuredLlmStageRunner s
private static string BuildPrompt(VisualBriefingManifest manifest, VisualBriefingPlanArtifact plan, VisualBriefingPresentationArtifact? parent) private static string BuildPrompt(VisualBriefingManifest manifest, VisualBriefingPlanArtifact plan, VisualBriefingPresentationArtifact? parent)
{ {
var parentJson = parent is null ? "none" : JsonSerializer.Serialize(new { parent.Layout, parent.Profile }, VisualBriefingJson.Compact); var parentJson = parent is null ? "none" : JsonSerializer.Serialize(new { parent.Layout, parent.Profile }, VisualBriefingJson.Canonical);
return $""" return $"""
Operation: {(parent is null ? "CREATE_DESIGN" : "CHANGE_DESIGN")} Operation: {(parent is null ? "CREATE_DESIGN" : "CHANGE_DESIGN")}
Design instruction: {manifest.Settings.Instruction} Design instruction: {manifest.Settings.Instruction}
Planned sections and components: Planned sections and components:
{JsonSerializer.Serialize(plan.Sections, VisualBriefingJson.Compact)} {JsonSerializer.Serialize(plan.Sections, VisualBriefingJson.Canonical)}
Parent design: Parent design:
{parentJson} {parentJson}
"""; """;

View File

@ -164,11 +164,11 @@ public sealed partial class VisualBriefingStore
return null; return null;
var hash = VisualBriefingHashing.ComputeSections( var hash = VisualBriefingHashing.ComputeSections(
JsonSerializer.Serialize(artifact.Facts, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.Facts, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(artifact.Metrics, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.Metrics, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(artifact.Tables, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.Tables, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(artifact.SourceCoverage, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.SourceCoverage, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(artifact.AssetPlan, VisualBriefingJson.Compact)); JsonSerializer.Serialize(artifact.AssetPlan, VisualBriefingJson.Canonical));
return string.Equals(hash, artifact.PayloadHash, StringComparison.Ordinal) ? artifact : null; return string.Equals(hash, artifact.PayloadHash, StringComparison.Ordinal) ? artifact : null;
} }
@ -217,7 +217,7 @@ public sealed partial class VisualBriefingStore
return null; return null;
var hash = VisualBriefingHashing.ComputeSections( var hash = VisualBriefingHashing.ComputeSections(
JsonSerializer.Serialize(artifact.Sections, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.Sections, VisualBriefingJson.Canonical),
artifact.StructuralSignature); artifact.StructuralSignature);
return string.Equals(hash, artifact.PayloadHash, StringComparison.Ordinal) ? artifact : null; return string.Equals(hash, artifact.PayloadHash, StringComparison.Ordinal) ? artifact : null;
@ -273,15 +273,15 @@ public sealed partial class VisualBriefingStore
return null; return null;
var payloadHash = VisualBriefingHashing.ComputeSections( var payloadHash = VisualBriefingHashing.ComputeSections(
JsonSerializer.Serialize(artifact.Slots, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.Slots, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(artifact.Charts, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.Charts, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(artifact.Controls, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.Controls, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(artifact.Formulas, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.Formulas, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(artifact.AccessibilityTexts, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.AccessibilityTexts, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(artifact.SourceReferences, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.SourceReferences, VisualBriefingJson.Canonical),
artifact.ResetLabel, artifact.ResetLabel,
JsonSerializer.Serialize(artifact.SourceCoverage, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.SourceCoverage, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(artifact.AssetPlan, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.AssetPlan, VisualBriefingJson.Canonical),
artifact.StructuralSignature); artifact.StructuralSignature);
return string.Equals(payloadHash, artifact.PayloadHash, StringComparison.Ordinal) ? artifact : null; return string.Equals(payloadHash, artifact.PayloadHash, StringComparison.Ordinal) ? artifact : null;
@ -336,7 +336,7 @@ public sealed partial class VisualBriefingStore
return null; return null;
var payloadHash = VisualBriefingHashing.ComputeSections( var payloadHash = VisualBriefingHashing.ComputeSections(
JsonSerializer.Serialize(artifact.Layout, VisualBriefingJson.Compact), JsonSerializer.Serialize(artifact.Layout, VisualBriefingJson.Canonical),
artifact.Profile.ToString(), artifact.Profile.ToString(),
artifact.TemplateHash, artifact.TemplateHash,
artifact.CssHash); artifact.CssHash);

View File

@ -504,15 +504,15 @@ public sealed partial class VisualBriefingStore
// Section order and count must match ReadContentArtifactAsync exactly: // Section order and count must match ReadContentArtifactAsync exactly:
var contentHash = VisualBriefingHashing.ComputeSections( var contentHash = VisualBriefingHashing.ComputeSections(
JsonSerializer.Serialize(importedSlots, VisualBriefingJson.Compact), JsonSerializer.Serialize(importedSlots, VisualBriefingJson.Canonical),
"[]", "[]",
"[]", "[]",
"[]", "[]",
"{}", "{}",
"{}", "{}",
"Reset", "Reset",
JsonSerializer.Serialize(coverage, VisualBriefingJson.Compact), JsonSerializer.Serialize(coverage, VisualBriefingJson.Canonical),
JsonSerializer.Serialize(assetPlan, VisualBriefingJson.Compact), JsonSerializer.Serialize(assetPlan, VisualBriefingJson.Canonical),
structuralSignature); structuralSignature);
var content = new VisualBriefingContentArtifact var content = new VisualBriefingContentArtifact
@ -551,7 +551,7 @@ public sealed partial class VisualBriefingStore
ArtifactId = Guid.NewGuid(), ArtifactId = Guid.NewGuid(),
CreatedAtUtc = DateTimeOffset.UtcNow, CreatedAtUtc = DateTimeOffset.UtcNow,
PayloadHash = VisualBriefingHashing.ComputeSections( PayloadHash = VisualBriefingHashing.ComputeSections(
JsonSerializer.Serialize(importedLayout, VisualBriefingJson.Compact), JsonSerializer.Serialize(importedLayout, VisualBriefingJson.Canonical),
VisualBriefingDesignProfile.EDITORIAL.ToString(), VisualBriefingDesignProfile.EDITORIAL.ToString(),
templateHash, templateHash,
cssHash), cssHash),
@ -607,7 +607,7 @@ public sealed partial class VisualBriefingStore
var businessData = VisualBriefingHashing.CanonicalJson(VisualBriefingData.RemoveProtectedData(parts.Data)); var businessData = VisualBriefingHashing.CanonicalJson(VisualBriefingData.RemoveProtectedData(parts.Data));
var assets = JsonSerializer.Serialize( var assets = JsonSerializer.Serialize(
VisualBriefingData.ExtractAssets(parts.Data), VisualBriefingData.ExtractAssets(parts.Data),
VisualBriefingJson.Compact); VisualBriefingJson.Canonical);
return new( return new(
VisualBriefingHashing.Compute(businessData), VisualBriefingHashing.Compute(businessData),

View File

@ -45,7 +45,7 @@ public sealed partial class VisualBriefingStore(
private const string TRANSCRIPTS_DIRECTORY_NAME = "transcripts"; private const string TRANSCRIPTS_DIRECTORY_NAME = "transcripts";
/// <summary>Gets the shared persistence JSON options.</summary> /// <summary>Gets the shared persistence JSON options.</summary>
private static readonly JsonSerializerOptions JSON_OPTIONS = VisualBriefingJson.Indented; private static readonly JsonSerializerOptions JSON_OPTIONS = VisualBriefingJson.Persistence;
/// <summary>Stores per-project process locks.</summary> /// <summary>Stores per-project process locks.</summary>
private readonly ConcurrentDictionary<Guid, SemaphoreSlim> briefingLocks = []; private readonly ConcurrentDictionary<Guid, SemaphoreSlim> briefingLocks = [];

View File

@ -184,7 +184,7 @@ internal static partial class VisualBriefingStructuredResponseProcessor
T? parsed; T? parsed;
try try
{ {
parsed = JsonSerializer.Deserialize<T>(candidate.Json, VisualBriefingJson.Compact); parsed = JsonSerializer.Deserialize<T>(candidate.Json, VisualBriefingJson.Canonical);
} }
catch (JsonException exception) catch (JsonException exception)
{ {

View File

@ -685,7 +685,7 @@ internal static partial class VisualBriefingValidation
private static bool ContainsForbidden<T>(T value) private static bool ContainsForbidden<T>(T value)
{ {
var json = JsonSerializer.SerializeToElement(value, VisualBriefingJson.Compact); var json = JsonSerializer.SerializeToElement(value, VisualBriefingJson.Canonical);
return ContainsForbiddenElement(json); return ContainsForbiddenElement(json);
} }