mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-08-24 20:52:11 +00:00
Replaced the duplicated payload hash formulas with one canonical definition
This commit is contained in:
parent
ebb9a6f135
commit
5f0b471443
@ -105,7 +105,13 @@ public sealed partial class VisualBriefingArtifactService
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Encodes the stable JSON artifact header for embedding in an HTML comment.
|
/// Encodes the stable JSON artifact header for embedding in an HTML comment.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static string EncodeHeader(VisualBriefingExportManifest exportManifest) => Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(exportManifest, JSON_OPTIONS)));
|
/// <remarks>
|
||||||
|
/// The header is canonical JSON because verifying a stored briefing encodes it again and compares
|
||||||
|
/// the document hash. Plain serialization would tie every stored document to the order in which the
|
||||||
|
/// manifest properties happen to be declared, so moving one property would reject every briefing
|
||||||
|
/// ever exported.
|
||||||
|
/// </remarks>
|
||||||
|
private static string EncodeHeader(VisualBriefingExportManifest exportManifest) => Convert.ToBase64String(Encoding.UTF8.GetBytes(VisualBriefingHashing.CanonicalJson(exportManifest)));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>RuntimeAIVersionRegex</c> for the visual briefing feature.
|
/// Defines <c>RuntimeAIVersionRegex</c> for the visual briefing feature.
|
||||||
|
|||||||
@ -150,12 +150,7 @@ internal sealed partial class VisualBriefingBuildOrchestrator
|
|||||||
{
|
{
|
||||||
ArtifactId = Guid.NewGuid(),
|
ArtifactId = Guid.NewGuid(),
|
||||||
CreatedAtUtc = DateTimeOffset.UtcNow,
|
CreatedAtUtc = DateTimeOffset.UtcNow,
|
||||||
PayloadHash = VisualBriefingHashing.ComputeSections(
|
PayloadHash = VisualBriefingPayloadHash.ForPresentation(previousPresentation.Layout, previousPresentation.Profile, compiled.TemplateHash, compiled.CssHash),
|
||||||
JsonSerializer.Serialize(previousPresentation.Layout, VisualBriefingJson.Canonical),
|
|
||||||
previousPresentation.Profile.ToString(),
|
|
||||||
compiled.TemplateHash,
|
|
||||||
compiled.CssHash),
|
|
||||||
|
|
||||||
Layout = previousPresentation.Layout,
|
Layout = previousPresentation.Layout,
|
||||||
Profile = previousPresentation.Profile,
|
Profile = previousPresentation.Profile,
|
||||||
TemplateHtml = compiled.TemplateHtml,
|
TemplateHtml = compiled.TemplateHtml,
|
||||||
|
|||||||
@ -67,18 +67,8 @@ internal sealed class VisualBriefingContentStage(StructuredLlmStageRunner stageR
|
|||||||
},
|
},
|
||||||
}, VisualBriefingJson.Canonical);
|
}, VisualBriefingJson.Canonical);
|
||||||
|
|
||||||
// Keep in sync with the verification in VisualBriefingStore.ReadContentArtifactAsync:
|
artifact.PayloadHash = VisualBriefingPayloadHash.ForContent(artifact.Slots, artifact.Charts, artifact.Controls, artifact.Formulas, artifact.AccessibilityTexts,
|
||||||
artifact.PayloadHash = VisualBriefingHashing.ComputeSections(
|
artifact.SourceReferences, artifact.ResetLabel, artifact.SourceCoverage, artifact.AssetPlan, artifact.StructuralSignature);
|
||||||
JsonSerializer.Serialize(artifact.Slots, VisualBriefingJson.Canonical),
|
|
||||||
JsonSerializer.Serialize(artifact.Charts, VisualBriefingJson.Canonical),
|
|
||||||
JsonSerializer.Serialize(artifact.Controls, VisualBriefingJson.Canonical),
|
|
||||||
JsonSerializer.Serialize(artifact.Formulas, VisualBriefingJson.Canonical),
|
|
||||||
JsonSerializer.Serialize(artifact.AccessibilityTexts, VisualBriefingJson.Canonical),
|
|
||||||
JsonSerializer.Serialize(artifact.SourceReferences, VisualBriefingJson.Canonical),
|
|
||||||
artifact.ResetLabel,
|
|
||||||
JsonSerializer.Serialize(artifact.SourceCoverage, VisualBriefingJson.Canonical),
|
|
||||||
JsonSerializer.Serialize(artifact.AssetPlan, VisualBriefingJson.Canonical),
|
|
||||||
artifact.StructuralSignature);
|
|
||||||
|
|
||||||
await store.WriteContentArtifactAsync(manifest.BriefingId, artifact, token);
|
await store.WriteContentArtifactAsync(manifest.BriefingId, artifact, token);
|
||||||
build.ContentArtifactId = artifact.ArtifactId;
|
build.ContentArtifactId = artifact.ArtifactId;
|
||||||
|
|||||||
@ -46,12 +46,7 @@ internal sealed class VisualBriefingEvidenceStage(StructuredLlmStageRunner stage
|
|||||||
await FailAsync(store, build, stage, run, VisualBriefingValidationRule.REFERENCE_INVALID, token);
|
await FailAsync(store, build, stage, run, VisualBriefingValidationRule.REFERENCE_INVALID, token);
|
||||||
|
|
||||||
var response = run.Response!;
|
var response = run.Response!;
|
||||||
var payloadHash = VisualBriefingHashing.ComputeSections(
|
var payloadHash = VisualBriefingPayloadHash.ForEvidence(response.Facts, response.Metrics, response.Tables, response.SourceCoverage, response.AssetPlan);
|
||||||
JsonSerializer.Serialize(response.Facts, VisualBriefingJson.Canonical),
|
|
||||||
JsonSerializer.Serialize(response.Metrics, VisualBriefingJson.Canonical),
|
|
||||||
JsonSerializer.Serialize(response.Tables, VisualBriefingJson.Canonical),
|
|
||||||
JsonSerializer.Serialize(response.SourceCoverage, VisualBriefingJson.Canonical),
|
|
||||||
JsonSerializer.Serialize(response.AssetPlan, VisualBriefingJson.Canonical));
|
|
||||||
|
|
||||||
var artifact = new VisualBriefingEvidenceArtifact
|
var artifact = new VisualBriefingEvidenceArtifact
|
||||||
{
|
{
|
||||||
|
|||||||
@ -42,6 +42,20 @@ internal static class VisualBriefingHashing
|
|||||||
return Convert.ToHexStringLower(await SHA256.HashDataAsync(stream, token));
|
return Convert.ToHexStringLower(await SHA256.HashDataAsync(stream, token));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns canonical JSON for one value, with ordinally sorted object properties.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Hashed values go through here instead of being serialized directly. Plain serialization writes
|
||||||
|
/// properties in declaration order, which would tie every stored hash to the order in which the
|
||||||
|
/// members happen to appear in the C# file: reordering two properties would invalidate every
|
||||||
|
/// briefing already on disk, without any visible change to the data.
|
||||||
|
/// </remarks>
|
||||||
|
/// <typeparam name="T">The type of the value to canonicalize.</typeparam>
|
||||||
|
/// <param name="value">The value to canonicalize.</param>
|
||||||
|
/// <returns>Compact canonical JSON.</returns>
|
||||||
|
internal static string CanonicalJson<T>(T value) => CanonicalJson(JsonSerializer.SerializeToElement(value, VisualBriefingJson.Canonical));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns canonical JSON with ordinally sorted object properties.
|
/// Returns canonical JSON with ordinally sorted object properties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -0,0 +1,102 @@
|
|||||||
|
namespace AIStudio.Assistants.VisualBriefing;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes the payload hashes that decide whether a stored intermediate artifact is still usable.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Each formula lives here exactly once. The stage that writes an artifact and the store that reads it
|
||||||
|
/// back have to agree on the sections down to their order, and they used to spell the formula out on
|
||||||
|
/// both sides with a comment asking the next developer to keep them aligned. A single misplaced section
|
||||||
|
/// makes the store discard every stored artifact of that kind, and it reports that as a missing
|
||||||
|
/// artifact rather than as an error, so the mistake surfaces as a briefing that silently refuses to be
|
||||||
|
/// reused. Sections are canonical JSON, which additionally makes the hashes independent of the order in
|
||||||
|
/// which the artifact properties are declared.
|
||||||
|
/// </remarks>
|
||||||
|
internal static class VisualBriefingPayloadHash
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Computes the payload hash of an evidence artifact.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="facts">The extracted facts.</param>
|
||||||
|
/// <param name="metrics">The extracted metrics.</param>
|
||||||
|
/// <param name="tables">The extracted tables.</param>
|
||||||
|
/// <param name="sourceCoverage">The per-source coverage.</param>
|
||||||
|
/// <param name="assetPlan">The planned visual assets.</param>
|
||||||
|
/// <returns>The payload hash.</returns>
|
||||||
|
internal static string ForEvidence(
|
||||||
|
List<VisualBriefingEvidenceFact> facts,
|
||||||
|
List<VisualBriefingEvidenceMetric> metrics,
|
||||||
|
List<VisualBriefingEvidenceTable> tables,
|
||||||
|
List<VisualBriefingSourceCoverage> sourceCoverage,
|
||||||
|
List<VisualBriefingAssetPlanItem> assetPlan) =>
|
||||||
|
VisualBriefingHashing.ComputeSections(
|
||||||
|
VisualBriefingHashing.CanonicalJson(facts),
|
||||||
|
VisualBriefingHashing.CanonicalJson(metrics),
|
||||||
|
VisualBriefingHashing.CanonicalJson(tables),
|
||||||
|
VisualBriefingHashing.CanonicalJson(sourceCoverage),
|
||||||
|
VisualBriefingHashing.CanonicalJson(assetPlan));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes the payload hash of a plan artifact.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sections">The planned sections.</param>
|
||||||
|
/// <param name="structuralSignature">The structural signature of the plan.</param>
|
||||||
|
/// <returns>The payload hash.</returns>
|
||||||
|
internal static string ForPlan(
|
||||||
|
List<VisualBriefingPlanSection> sections,
|
||||||
|
string structuralSignature) => VisualBriefingHashing.ComputeSections(VisualBriefingHashing.CanonicalJson(sections), structuralSignature);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes the payload hash of a content artifact.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slots">The filled content slots.</param>
|
||||||
|
/// <param name="charts">The chart specifications.</param>
|
||||||
|
/// <param name="controls">The interactive control specifications.</param>
|
||||||
|
/// <param name="formulas">The formula specifications.</param>
|
||||||
|
/// <param name="accessibilityTexts">The accessibility texts per component.</param>
|
||||||
|
/// <param name="sourceReferences">The source references per component.</param>
|
||||||
|
/// <param name="resetLabel">The localized reset label.</param>
|
||||||
|
/// <param name="sourceCoverage">The per-source coverage.</param>
|
||||||
|
/// <param name="assetPlan">The planned visual assets.</param>
|
||||||
|
/// <param name="structuralSignature">The structural signature of the business data.</param>
|
||||||
|
/// <returns>The payload hash.</returns>
|
||||||
|
internal static string ForContent(
|
||||||
|
List<VisualBriefingSlotValue> slots,
|
||||||
|
List<VisualBriefingChartSpec> charts,
|
||||||
|
List<VisualBriefingControlSpec> controls,
|
||||||
|
List<VisualBriefingFormulaSpec> formulas,
|
||||||
|
Dictionary<string, string> accessibilityTexts,
|
||||||
|
Dictionary<string, List<string>> sourceReferences,
|
||||||
|
string resetLabel,
|
||||||
|
List<VisualBriefingSourceCoverage> sourceCoverage,
|
||||||
|
List<VisualBriefingAssetPlanItem> assetPlan,
|
||||||
|
string structuralSignature) =>
|
||||||
|
VisualBriefingHashing.ComputeSections(
|
||||||
|
VisualBriefingHashing.CanonicalJson(slots),
|
||||||
|
VisualBriefingHashing.CanonicalJson(charts),
|
||||||
|
VisualBriefingHashing.CanonicalJson(controls),
|
||||||
|
VisualBriefingHashing.CanonicalJson(formulas),
|
||||||
|
VisualBriefingHashing.CanonicalJson(accessibilityTexts),
|
||||||
|
VisualBriefingHashing.CanonicalJson(sourceReferences),
|
||||||
|
resetLabel,
|
||||||
|
VisualBriefingHashing.CanonicalJson(sourceCoverage),
|
||||||
|
VisualBriefingHashing.CanonicalJson(assetPlan),
|
||||||
|
structuralSignature);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes the payload hash of a presentation artifact.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="layout">The compiled layout tree.</param>
|
||||||
|
/// <param name="profile">The design profile.</param>
|
||||||
|
/// <param name="templateHash">The hash of the compiled template.</param>
|
||||||
|
/// <param name="cssHash">The hash of the compiled CSS.</param>
|
||||||
|
/// <returns>The payload hash.</returns>
|
||||||
|
internal static string ForPresentation(
|
||||||
|
VisualBriefingLayoutNode layout,
|
||||||
|
VisualBriefingDesignProfile profile,
|
||||||
|
string templateHash,
|
||||||
|
string cssHash) =>
|
||||||
|
VisualBriefingHashing.ComputeSections(
|
||||||
|
VisualBriefingHashing.CanonicalJson(layout),
|
||||||
|
profile.ToString(), templateHash, cssHash);
|
||||||
|
}
|
||||||
@ -50,8 +50,6 @@ 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.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)
|
||||||
.Select(component =>
|
.Select(component =>
|
||||||
@ -61,7 +59,7 @@ internal sealed class VisualBriefingPlanStage(StructuredLlmStageRunner stageRunn
|
|||||||
{
|
{
|
||||||
ArtifactId = Guid.NewGuid(),
|
ArtifactId = Guid.NewGuid(),
|
||||||
CreatedAtUtc = DateTimeOffset.UtcNow,
|
CreatedAtUtc = DateTimeOffset.UtcNow,
|
||||||
PayloadHash = VisualBriefingHashing.ComputeSections(payload, structuralSignature),
|
PayloadHash = VisualBriefingPayloadHash.ForPlan(sections, structuralSignature),
|
||||||
Sections = sections,
|
Sections = sections,
|
||||||
StructuralSignature = structuralSignature,
|
StructuralSignature = structuralSignature,
|
||||||
Model = VisualBriefingModelNames.ExportLabel(provider),
|
Model = VisualBriefingModelNames.ExportLabel(provider),
|
||||||
|
|||||||
@ -80,11 +80,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 = VisualBriefingPayloadHash.ForPresentation(run.Response.Layout, run.Response.Profile, compiled.TemplateHash, compiled.CssHash);
|
||||||
JsonSerializer.Serialize(run.Response.Layout, VisualBriefingJson.Canonical),
|
|
||||||
run.Response.Profile.ToString(),
|
|
||||||
compiled.TemplateHash,
|
|
||||||
compiled.CssHash);
|
|
||||||
|
|
||||||
var artifact = new VisualBriefingPresentationArtifact
|
var artifact = new VisualBriefingPresentationArtifact
|
||||||
{
|
{
|
||||||
|
|||||||
@ -163,13 +163,7 @@ public sealed partial class VisualBriefingStore
|
|||||||
artifact.ArtifactId != artifactId)
|
artifact.ArtifactId != artifactId)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var hash = VisualBriefingHashing.ComputeSections(
|
var hash = VisualBriefingPayloadHash.ForEvidence(artifact.Facts, artifact.Metrics, artifact.Tables, artifact.SourceCoverage, artifact.AssetPlan);
|
||||||
JsonSerializer.Serialize(artifact.Facts, VisualBriefingJson.Canonical),
|
|
||||||
JsonSerializer.Serialize(artifact.Metrics, VisualBriefingJson.Canonical),
|
|
||||||
JsonSerializer.Serialize(artifact.Tables, VisualBriefingJson.Canonical),
|
|
||||||
JsonSerializer.Serialize(artifact.SourceCoverage, VisualBriefingJson.Canonical),
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,9 +210,7 @@ public sealed partial class VisualBriefingStore
|
|||||||
artifact.ArtifactId != artifactId)
|
artifact.ArtifactId != artifactId)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var hash = VisualBriefingHashing.ComputeSections(
|
var hash = VisualBriefingPayloadHash.ForPlan(artifact.Sections, artifact.StructuralSignature);
|
||||||
JsonSerializer.Serialize(artifact.Sections, VisualBriefingJson.Canonical),
|
|
||||||
artifact.StructuralSignature);
|
|
||||||
|
|
||||||
return string.Equals(hash, artifact.PayloadHash, StringComparison.Ordinal) ? artifact : null;
|
return string.Equals(hash, artifact.PayloadHash, StringComparison.Ordinal) ? artifact : null;
|
||||||
}
|
}
|
||||||
@ -272,17 +264,8 @@ public sealed partial class VisualBriefingStore
|
|||||||
string.IsNullOrWhiteSpace(artifact.ResetLabel))
|
string.IsNullOrWhiteSpace(artifact.ResetLabel))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var payloadHash = VisualBriefingHashing.ComputeSections(
|
var payloadHash = VisualBriefingPayloadHash.ForContent(artifact.Slots, artifact.Charts, artifact.Controls, artifact.Formulas, artifact.AccessibilityTexts,
|
||||||
JsonSerializer.Serialize(artifact.Slots, VisualBriefingJson.Canonical),
|
artifact.SourceReferences, artifact.ResetLabel, artifact.SourceCoverage, artifact.AssetPlan, artifact.StructuralSignature);
|
||||||
JsonSerializer.Serialize(artifact.Charts, VisualBriefingJson.Canonical),
|
|
||||||
JsonSerializer.Serialize(artifact.Controls, VisualBriefingJson.Canonical),
|
|
||||||
JsonSerializer.Serialize(artifact.Formulas, VisualBriefingJson.Canonical),
|
|
||||||
JsonSerializer.Serialize(artifact.AccessibilityTexts, VisualBriefingJson.Canonical),
|
|
||||||
JsonSerializer.Serialize(artifact.SourceReferences, VisualBriefingJson.Canonical),
|
|
||||||
artifact.ResetLabel,
|
|
||||||
JsonSerializer.Serialize(artifact.SourceCoverage, VisualBriefingJson.Canonical),
|
|
||||||
JsonSerializer.Serialize(artifact.AssetPlan, VisualBriefingJson.Canonical),
|
|
||||||
artifact.StructuralSignature);
|
|
||||||
|
|
||||||
return string.Equals(payloadHash, artifact.PayloadHash, StringComparison.Ordinal) ? artifact : null;
|
return string.Equals(payloadHash, artifact.PayloadHash, StringComparison.Ordinal) ? artifact : null;
|
||||||
}
|
}
|
||||||
@ -335,12 +318,7 @@ public sealed partial class VisualBriefingStore
|
|||||||
artifact.ArtifactId != artifactId)
|
artifact.ArtifactId != artifactId)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var payloadHash = VisualBriefingHashing.ComputeSections(
|
var payloadHash = VisualBriefingPayloadHash.ForPresentation(artifact.Layout, artifact.Profile, artifact.TemplateHash, artifact.CssHash);
|
||||||
JsonSerializer.Serialize(artifact.Layout, VisualBriefingJson.Canonical),
|
|
||||||
artifact.Profile.ToString(),
|
|
||||||
artifact.TemplateHash,
|
|
||||||
artifact.CssHash);
|
|
||||||
|
|
||||||
return string.Equals(payloadHash, artifact.PayloadHash, StringComparison.Ordinal) &&
|
return string.Equals(payloadHash, artifact.PayloadHash, StringComparison.Ordinal) &&
|
||||||
string.Equals(
|
string.Equals(
|
||||||
VisualBriefingHashing.Compute(artifact.TemplateHtml),
|
VisualBriefingHashing.Compute(artifact.TemplateHtml),
|
||||||
|
|||||||
@ -502,24 +502,10 @@ public sealed partial class VisualBriefingStore
|
|||||||
new() { SlotId = "imported_data", Value = businessData },
|
new() { SlotId = "imported_data", Value = businessData },
|
||||||
};
|
};
|
||||||
|
|
||||||
// Section order and count must match ReadContentArtifactAsync exactly:
|
|
||||||
var contentHash = VisualBriefingHashing.ComputeSections(
|
|
||||||
JsonSerializer.Serialize(importedSlots, VisualBriefingJson.Canonical),
|
|
||||||
"[]",
|
|
||||||
"[]",
|
|
||||||
"[]",
|
|
||||||
"{}",
|
|
||||||
"{}",
|
|
||||||
"Reset",
|
|
||||||
JsonSerializer.Serialize(coverage, VisualBriefingJson.Canonical),
|
|
||||||
JsonSerializer.Serialize(assetPlan, VisualBriefingJson.Canonical),
|
|
||||||
structuralSignature);
|
|
||||||
|
|
||||||
var content = new VisualBriefingContentArtifact
|
var content = new VisualBriefingContentArtifact
|
||||||
{
|
{
|
||||||
ArtifactId = Guid.NewGuid(),
|
ArtifactId = Guid.NewGuid(),
|
||||||
CreatedAtUtc = DateTimeOffset.UtcNow,
|
CreatedAtUtc = DateTimeOffset.UtcNow,
|
||||||
PayloadHash = contentHash,
|
|
||||||
Data = businessData,
|
Data = businessData,
|
||||||
Slots = importedSlots,
|
Slots = importedSlots,
|
||||||
ResetLabel = "Reset",
|
ResetLabel = "Reset",
|
||||||
@ -529,6 +515,12 @@ public sealed partial class VisualBriefingStore
|
|||||||
Model = "Imported artifact",
|
Model = "Imported artifact",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// An imported briefing carries no charts, controls, formulas, accessibility texts, or source
|
||||||
|
// references. Hashing the artifact itself keeps those empty sections in the right places
|
||||||
|
// without spelling them out as literals here.
|
||||||
|
content.PayloadHash = VisualBriefingPayloadHash.ForContent(content.Slots, content.Charts, content.Controls, content.Formulas, content.AccessibilityTexts,
|
||||||
|
content.SourceReferences, content.ResetLabel, content.SourceCoverage, content.AssetPlan, content.StructuralSignature);
|
||||||
|
|
||||||
var importedLayout = new VisualBriefingLayoutNode
|
var importedLayout = new VisualBriefingLayoutNode
|
||||||
{
|
{
|
||||||
NodeId = "imported",
|
NodeId = "imported",
|
||||||
@ -550,11 +542,7 @@ public sealed partial class VisualBriefingStore
|
|||||||
{
|
{
|
||||||
ArtifactId = Guid.NewGuid(),
|
ArtifactId = Guid.NewGuid(),
|
||||||
CreatedAtUtc = DateTimeOffset.UtcNow,
|
CreatedAtUtc = DateTimeOffset.UtcNow,
|
||||||
PayloadHash = VisualBriefingHashing.ComputeSections(
|
PayloadHash = VisualBriefingPayloadHash.ForPresentation(importedLayout, VisualBriefingDesignProfile.EDITORIAL, templateHash, cssHash),
|
||||||
JsonSerializer.Serialize(importedLayout, VisualBriefingJson.Canonical),
|
|
||||||
VisualBriefingDesignProfile.EDITORIAL.ToString(),
|
|
||||||
templateHash,
|
|
||||||
cssHash),
|
|
||||||
Layout = importedLayout,
|
Layout = importedLayout,
|
||||||
Profile = VisualBriefingDesignProfile.EDITORIAL,
|
Profile = VisualBriefingDesignProfile.EDITORIAL,
|
||||||
TemplateHtml = parts.TemplateHtml,
|
TemplateHtml = parts.TemplateHtml,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user