mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-08-11 22:52:10 +00:00
Added rule MWAIS0011 to pin the JSON shape of hashed types
This commit is contained in:
parent
22331912eb
commit
f1bcfbc2c0
@ -6,23 +6,24 @@ namespace AIStudio.Assistants.VisualBriefing;
|
|||||||
/// Describes one visual asset without embedding its bytes.
|
/// Describes one visual asset without embedding its bytes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
||||||
|
[CanonicalJsonShape("d05cdc87")]
|
||||||
public sealed class VisualBriefingAssetPlanItem
|
public sealed class VisualBriefingAssetPlanItem
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the stable visual asset identifier.
|
/// Gets or sets the stable visual asset identifier.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
public string AssetId { get; set; } = string.Empty;
|
public string AssetId { get; init; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the model's visual description for presentation decisions.
|
/// Gets or sets the model's visual description for presentation decisions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
public string Description { get; set; } = string.Empty;
|
public string Description { get; init; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the target-language text alternative.
|
/// Gets or sets the target-language text alternative.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
public string AltText { get; set; } = string.Empty;
|
public string AltText { get; init; } = string.Empty;
|
||||||
}
|
}
|
||||||
@ -6,6 +6,7 @@ namespace AIStudio.Assistants.VisualBriefing;
|
|||||||
/// Defines one named numeric series in a chart specification.
|
/// Defines one named numeric series in a chart specification.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
||||||
|
[CanonicalJsonShape("57679f28")]
|
||||||
public sealed class VisualBriefingChartSeries
|
public sealed class VisualBriefingChartSeries
|
||||||
{
|
{
|
||||||
/// <summary>Gets or sets the series name.</summary>
|
/// <summary>Gets or sets the series name.</summary>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ namespace AIStudio.Assistants.VisualBriefing;
|
|||||||
/// Defines the bounded semantic input for one compiled chart.
|
/// Defines the bounded semantic input for one compiled chart.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
||||||
|
[CanonicalJsonShape("68b2ff45")]
|
||||||
public sealed class VisualBriefingChartSpec
|
public sealed class VisualBriefingChartSpec
|
||||||
{
|
{
|
||||||
/// <summary>Gets or sets the owning component identifier.</summary>
|
/// <summary>Gets or sets the owning component identifier.</summary>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ namespace AIStudio.Assistants.VisualBriefing;
|
|||||||
/// Defines one value and visible label offered by an interaction control.
|
/// Defines one value and visible label offered by an interaction control.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
||||||
|
[CanonicalJsonShape("08092336")]
|
||||||
public sealed class VisualBriefingControlOption
|
public sealed class VisualBriefingControlOption
|
||||||
{
|
{
|
||||||
/// <summary>Gets or sets the stored option value.</summary>
|
/// <summary>Gets or sets the stored option value.</summary>
|
||||||
|
|||||||
@ -7,6 +7,7 @@ namespace AIStudio.Assistants.VisualBriefing;
|
|||||||
/// Defines one bounded declarative interaction control.
|
/// Defines one bounded declarative interaction control.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
||||||
|
[CanonicalJsonShape("42306121")]
|
||||||
public sealed class VisualBriefingControlSpec
|
public sealed class VisualBriefingControlSpec
|
||||||
{
|
{
|
||||||
/// <summary>Gets or sets the globally unique control identifier.</summary>
|
/// <summary>Gets or sets the globally unique control identifier.</summary>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ namespace AIStudio.Assistants.VisualBriefing;
|
|||||||
/// Describes one sourced factual statement extracted during evidence analysis.
|
/// Describes one sourced factual statement extracted during evidence analysis.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
||||||
|
[CanonicalJsonShape("7857e7da")]
|
||||||
public sealed class VisualBriefingEvidenceFact
|
public sealed class VisualBriefingEvidenceFact
|
||||||
{
|
{
|
||||||
/// <summary>Gets or sets the stable evidence identifier.</summary>
|
/// <summary>Gets or sets the stable evidence identifier.</summary>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ namespace AIStudio.Assistants.VisualBriefing;
|
|||||||
/// Describes one sourced numeric metric extracted during evidence analysis.
|
/// Describes one sourced numeric metric extracted during evidence analysis.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
||||||
|
[CanonicalJsonShape("08d12050")]
|
||||||
public sealed class VisualBriefingEvidenceMetric
|
public sealed class VisualBriefingEvidenceMetric
|
||||||
{
|
{
|
||||||
/// <summary>Gets or sets the stable evidence identifier.</summary>
|
/// <summary>Gets or sets the stable evidence identifier.</summary>
|
||||||
|
|||||||
@ -7,6 +7,7 @@ namespace AIStudio.Assistants.VisualBriefing;
|
|||||||
/// Describes one sourced table extracted during evidence analysis.
|
/// Describes one sourced table extracted during evidence analysis.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
||||||
|
[CanonicalJsonShape("ad23c5b0")]
|
||||||
public sealed class VisualBriefingEvidenceTable
|
public sealed class VisualBriefingEvidenceTable
|
||||||
{
|
{
|
||||||
/// <summary>Gets or sets the stable evidence identifier.</summary>
|
/// <summary>Gets or sets the stable evidence identifier.</summary>
|
||||||
|
|||||||
@ -5,107 +5,108 @@ namespace AIStudio.Assistants.VisualBriefing;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>VisualBriefingExportManifest</c> for the visual briefing feature.
|
/// Defines <c>VisualBriefingExportManifest</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[CanonicalJsonShape("fc2235e8")]
|
||||||
public sealed class VisualBriefingExportManifest
|
public sealed class VisualBriefingExportManifest
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>ArtifactVersion</c> for the visual briefing feature.
|
/// Defines <c>ArtifactVersion</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int ArtifactVersion { get; set; } = VisualBriefingVersions.ARTIFACT;
|
public int ArtifactVersion { get; init; } = VisualBriefingVersions.ARTIFACT;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>SchemaVersion</c> for the visual briefing feature.
|
/// Defines <c>SchemaVersion</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int SchemaVersion { get; set; } = VisualBriefingVersions.SCHEMA;
|
public int SchemaVersion { get; init; } = VisualBriefingVersions.SCHEMA;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>RuntimeVersion</c> for the visual briefing feature.
|
/// Defines <c>RuntimeVersion</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int RuntimeVersion { get; set; } = VisualBriefingVersions.RUNTIME;
|
public int RuntimeVersion { get; init; } = VisualBriefingVersions.RUNTIME;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>BriefingId</c> for the visual briefing feature.
|
/// Defines <c>BriefingId</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Guid BriefingId { get; set; }
|
public Guid BriefingId { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>RevisionId</c> for the visual briefing feature.
|
/// Defines <c>RevisionId</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Guid RevisionId { get; set; }
|
public Guid RevisionId { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>ParentRevisionId</c> for the visual briefing feature.
|
/// Defines <c>ParentRevisionId</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Guid? ParentRevisionId { get; set; }
|
public Guid? ParentRevisionId { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>Name</c> for the visual briefing feature.
|
/// Defines <c>Name</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; init; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>Author</c> for the visual briefing feature.
|
/// Defines <c>Author</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Author { get; set; } = string.Empty;
|
public string Author { get; init; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>CreatedAtUtc</c> for the visual briefing feature.
|
/// Defines <c>CreatedAtUtc</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTimeOffset CreatedAtUtc { get; set; }
|
public DateTimeOffset CreatedAtUtc { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>TargetLanguage</c> for the visual briefing feature.
|
/// Defines <c>TargetLanguage</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommonLanguages TargetLanguage { get; set; }
|
public CommonLanguages TargetLanguage { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>CustomTargetLanguage</c> for the visual briefing feature.
|
/// Defines <c>CustomTargetLanguage</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string CustomTargetLanguage { get; set; } = string.Empty;
|
public string CustomTargetLanguage { get; init; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>AudienceProfile</c> for the visual briefing feature.
|
/// Defines <c>AudienceProfile</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public AudienceProfile AudienceProfile { get; set; }
|
public AudienceProfile AudienceProfile { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>AudienceAgeGroup</c> for the visual briefing feature.
|
/// Defines <c>AudienceAgeGroup</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public AudienceAgeGroup AudienceAgeGroup { get; set; }
|
public AudienceAgeGroup AudienceAgeGroup { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>AudienceOrganizationalLevel</c> for the visual briefing feature.
|
/// Defines <c>AudienceOrganizationalLevel</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public AudienceOrganizationalLevel AudienceOrganizationalLevel { get; set; }
|
public AudienceOrganizationalLevel AudienceOrganizationalLevel { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>AudienceExpertise</c> for the visual briefing feature.
|
/// Defines <c>AudienceExpertise</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public AudienceExpertise AudienceExpertise { get; set; }
|
public AudienceExpertise AudienceExpertise { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>ShowSourceReferences</c> for the visual briefing feature.
|
/// Defines <c>ShowSourceReferences</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ShowSourceReferences { get; set; }
|
public bool ShowSourceReferences { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>ProtectionLevel</c> for the visual briefing feature.
|
/// Defines <c>ProtectionLevel</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public VisualBriefingProtectionLevel ProtectionLevel { get; set; }
|
public VisualBriefingProtectionLevel ProtectionLevel { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>CustomProtectionLevel</c> for the visual briefing feature.
|
/// Defines <c>CustomProtectionLevel</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string CustomProtectionLevel { get; set; } = string.Empty;
|
public string CustomProtectionLevel { get; init; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>AIStudioVersion</c> for the visual briefing feature.
|
/// Defines <c>AIStudioVersion</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string AIStudioVersion { get; set; } = string.Empty;
|
public string AIStudioVersion { get; init; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>RuntimeAIStudioVersion</c> for the visual briefing feature.
|
/// Defines <c>RuntimeAIStudioVersion</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string RuntimeAIStudioVersion { get; set; } = string.Empty;
|
public string RuntimeAIStudioVersion { get; init; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the SHA-256 hash of the complete standalone HTML document.
|
/// Gets or sets the SHA-256 hash of the complete standalone HTML document.
|
||||||
|
|||||||
@ -6,6 +6,7 @@ namespace AIStudio.Assistants.VisualBriefing;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines <c>VisualBriefingFormulaNode</c> for the visual briefing feature.
|
/// Defines <c>VisualBriefingFormulaNode</c> for the visual briefing feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[CanonicalJsonShape("aa29e015")]
|
||||||
public sealed class VisualBriefingFormulaNode
|
public sealed class VisualBriefingFormulaNode
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ namespace AIStudio.Assistants.VisualBriefing;
|
|||||||
/// Connects one deterministic formula tree to a component result slot.
|
/// Connects one deterministic formula tree to a component result slot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
||||||
|
[CanonicalJsonShape("b644b191")]
|
||||||
public sealed class VisualBriefingFormulaSpec
|
public sealed class VisualBriefingFormulaSpec
|
||||||
{
|
{
|
||||||
/// <summary>Gets or sets the owning component identifier.</summary>
|
/// <summary>Gets or sets the owning component identifier.</summary>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ namespace AIStudio.Assistants.VisualBriefing;
|
|||||||
/// Defines one node in the validated bounded presentation layout tree.
|
/// Defines one node in the validated bounded presentation layout tree.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
||||||
|
[CanonicalJsonShape("14064835")]
|
||||||
public sealed class VisualBriefingLayoutNode
|
public sealed class VisualBriefingLayoutNode
|
||||||
{
|
{
|
||||||
/// <summary>Gets or sets the globally unique layout node identifier.</summary>
|
/// <summary>Gets or sets the globally unique layout node identifier.</summary>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ namespace AIStudio.Assistants.VisualBriefing;
|
|||||||
/// Plans one semantic component and its evidence and content dependencies.
|
/// Plans one semantic component and its evidence and content dependencies.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
||||||
|
[CanonicalJsonShape("bdafbeaf")]
|
||||||
public sealed class VisualBriefingPlanComponent
|
public sealed class VisualBriefingPlanComponent
|
||||||
{
|
{
|
||||||
/// <summary>Gets or sets the globally unique component identifier.</summary>
|
/// <summary>Gets or sets the globally unique component identifier.</summary>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ namespace AIStudio.Assistants.VisualBriefing;
|
|||||||
/// Plans one narrative section and its ordered components.
|
/// Plans one narrative section and its ordered components.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
||||||
|
[CanonicalJsonShape("91d1394d")]
|
||||||
public sealed class VisualBriefingPlanSection
|
public sealed class VisualBriefingPlanSection
|
||||||
{
|
{
|
||||||
/// <summary>Gets or sets the globally unique section identifier.</summary>
|
/// <summary>Gets or sets the globally unique section identifier.</summary>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ namespace AIStudio.Assistants.VisualBriefing;
|
|||||||
/// Plans one semantic content slot owned by a component.
|
/// Plans one semantic content slot owned by a component.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
||||||
|
[CanonicalJsonShape("04cc2e77")]
|
||||||
public sealed class VisualBriefingPlanSlot
|
public sealed class VisualBriefingPlanSlot
|
||||||
{
|
{
|
||||||
/// <summary>Gets or sets the globally unique slot identifier.</summary>
|
/// <summary>Gets or sets the globally unique slot identifier.</summary>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ namespace AIStudio.Assistants.VisualBriefing;
|
|||||||
/// Defines bounded responsive column counts for one grid layout node.
|
/// Defines bounded responsive column counts for one grid layout node.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
||||||
|
[CanonicalJsonShape("92c96e68")]
|
||||||
public sealed class VisualBriefingResponsiveColumns
|
public sealed class VisualBriefingResponsiveColumns
|
||||||
{
|
{
|
||||||
/// <summary>Gets or sets the mobile column count.</summary>
|
/// <summary>Gets or sets the mobile column count.</summary>
|
||||||
|
|||||||
@ -7,6 +7,7 @@ namespace AIStudio.Assistants.VisualBriefing;
|
|||||||
/// Assigns a validated JSON value to one planned semantic slot.
|
/// Assigns a validated JSON value to one planned semantic slot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
||||||
|
[CanonicalJsonShape("6cfa3f02")]
|
||||||
public sealed class VisualBriefingSlotValue
|
public sealed class VisualBriefingSlotValue
|
||||||
{
|
{
|
||||||
/// <summary>Gets or sets the planned slot identifier.</summary>
|
/// <summary>Gets or sets the planned slot identifier.</summary>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ namespace AIStudio.Assistants.VisualBriefing;
|
|||||||
/// Records how one source contributed to canonical content.
|
/// Records how one source contributed to canonical content.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
|
||||||
|
[CanonicalJsonShape("b1535c0e")]
|
||||||
public sealed class VisualBriefingSourceCoverage
|
public sealed class VisualBriefingSourceCoverage
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
27
app/MindWork AI Studio/Tools/CanonicalJsonShapeAttribute.cs
Normal file
27
app/MindWork AI Studio/Tools/CanonicalJsonShapeAttribute.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
namespace AIStudio.Tools;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pins the JSON shape of a type whose serialized form is hashed into stored data.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A Roslyn analyzer only ever sees the current code, so it cannot notice that a property was added
|
||||||
|
/// yesterday. Declaring the expected shape here gives it something to compare against: rule MWAIS0011
|
||||||
|
/// derives a signature from the properties, their JSON names, their types, and their ignore conditions,
|
||||||
|
/// and fails the build when it no longer matches. The point is not the value itself but the moment it
|
||||||
|
/// forces — updating it is the step where somebody has to decide whether existing stored data may stop
|
||||||
|
/// being readable, and the changed value makes that decision visible in the diff.
|
||||||
|
/// Only types whose JSON is hashed directly carry this attribute. The artifact envelopes around them do
|
||||||
|
/// not, because the parts of them that reach a hash are named one by one in
|
||||||
|
/// <c>VisualBriefingPayloadHash</c>, where changing a type breaks the build on its own.
|
||||||
|
/// Attributes that affect reading rather than writing, such as <c>JsonRequired</c>, are not part of the
|
||||||
|
/// signature: they cannot change the bytes that were hashed.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="signature">The expected shape signature, reported by MWAIS0011 whenever it changes.</param>
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public sealed class CanonicalJsonShapeAttribute(string signature) : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the expected shape signature.
|
||||||
|
/// </summary>
|
||||||
|
public string Signature { get; } = signature;
|
||||||
|
}
|
||||||
@ -13,4 +13,5 @@
|
|||||||
MWAIS0007 | Usage | Error | EmptyStringAnalyzer
|
MWAIS0007 | Usage | Error | EmptyStringAnalyzer
|
||||||
MWAIS0008 | Naming | Error | LocalConstantsAnalyzer
|
MWAIS0008 | Naming | Error | LocalConstantsAnalyzer
|
||||||
MWAIS0009 | Usage | Error | StaticServiceProviderCacheAnalyzer
|
MWAIS0009 | Usage | Error | StaticServiceProviderCacheAnalyzer
|
||||||
MWAIS0010 | Usage | Error | CanonicalJsonConfigurationAnalyzer
|
MWAIS0010 | Usage | Error | CanonicalJsonConfigurationAnalyzer
|
||||||
|
MWAIS0011 | Usage | Error | CanonicalJsonShapeAnalyzer
|
||||||
@ -12,4 +12,5 @@ public static class Identifier
|
|||||||
public const string LOCAL_CONSTANTS_ANALYZER = $"{Tools.ID_PREFIX}0008";
|
public const string LOCAL_CONSTANTS_ANALYZER = $"{Tools.ID_PREFIX}0008";
|
||||||
public const string STATIC_SERVICE_PROVIDER_CACHE_ANALYZER = $"{Tools.ID_PREFIX}0009";
|
public const string STATIC_SERVICE_PROVIDER_CACHE_ANALYZER = $"{Tools.ID_PREFIX}0009";
|
||||||
public const string CANONICAL_JSON_CONFIGURATION_ANALYZER = $"{Tools.ID_PREFIX}0010";
|
public const string CANONICAL_JSON_CONFIGURATION_ANALYZER = $"{Tools.ID_PREFIX}0010";
|
||||||
|
public const string CANONICAL_JSON_SHAPE_ANALYZER = $"{Tools.ID_PREFIX}0011";
|
||||||
}
|
}
|
||||||
@ -35,13 +35,13 @@ public sealed class CanonicalJsonConfigurationAnalyzer : DiagnosticAnalyzer
|
|||||||
{
|
{
|
||||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||||
context.EnableConcurrentExecution();
|
context.EnableConcurrentExecution();
|
||||||
context.RegisterSyntaxNodeAction(this.AnalyzeProperty, SyntaxKind.PropertyDeclaration);
|
context.RegisterSyntaxNodeAction(AnalyzeProperty, SyntaxKind.PropertyDeclaration);
|
||||||
context.RegisterSyntaxNodeAction(this.AnalyzeField, SyntaxKind.FieldDeclaration);
|
context.RegisterSyntaxNodeAction(AnalyzeField, SyntaxKind.FieldDeclaration);
|
||||||
context.RegisterSyntaxNodeAction(AnalyzeMemberAccess, SyntaxKind.SimpleMemberAccessExpression);
|
context.RegisterSyntaxNodeAction(AnalyzeMemberAccess, SyntaxKind.SimpleMemberAccessExpression);
|
||||||
context.RegisterSyntaxNodeAction(this.AnalyzeAssignment, SyntaxKind.SimpleAssignmentExpression);
|
context.RegisterSyntaxNodeAction(AnalyzeAssignment, SyntaxKind.SimpleAssignmentExpression);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AnalyzeProperty(SyntaxNodeAnalysisContext context)
|
private static void AnalyzeProperty(SyntaxNodeAnalysisContext context)
|
||||||
{
|
{
|
||||||
var declaration = (PropertyDeclarationSyntax)context.Node;
|
var declaration = (PropertyDeclarationSyntax)context.Node;
|
||||||
if (context.SemanticModel.GetDeclaredSymbol(declaration) is not { } symbol || !IsMarked(symbol))
|
if (context.SemanticModel.GetDeclaredSymbol(declaration) is not { } symbol || !IsMarked(symbol))
|
||||||
@ -50,7 +50,7 @@ public sealed class CanonicalJsonConfigurationAnalyzer : DiagnosticAnalyzer
|
|||||||
AnalyzeInitializer(context, declaration.Initializer?.Value, declaration.Identifier.GetLocation());
|
AnalyzeInitializer(context, declaration.Initializer?.Value, declaration.Identifier.GetLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AnalyzeField(SyntaxNodeAnalysisContext context)
|
private static void AnalyzeField(SyntaxNodeAnalysisContext context)
|
||||||
{
|
{
|
||||||
var declaration = (FieldDeclarationSyntax)context.Node;
|
var declaration = (FieldDeclarationSyntax)context.Node;
|
||||||
foreach (var variable in declaration.Declaration.Variables)
|
foreach (var variable in declaration.Declaration.Variables)
|
||||||
@ -122,7 +122,7 @@ public sealed class CanonicalJsonConfigurationAnalyzer : DiagnosticAnalyzer
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reports assigning any setting of already declared canonical options.
|
/// Reports assigning any setting of already declared canonical options.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void AnalyzeAssignment(SyntaxNodeAnalysisContext context)
|
private static void AnalyzeAssignment(SyntaxNodeAnalysisContext context)
|
||||||
{
|
{
|
||||||
var assignment = (AssignmentExpressionSyntax)context.Node;
|
var assignment = (AssignmentExpressionSyntax)context.Node;
|
||||||
if (assignment.Left is not MemberAccessExpressionSyntax memberAccess)
|
if (assignment.Left is not MemberAccessExpressionSyntax memberAccess)
|
||||||
|
|||||||
@ -0,0 +1,149 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
|
namespace SourceCodeRules.UsageAnalyzers;
|
||||||
|
|
||||||
|
#pragma warning disable RS1038
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
#pragma warning restore RS1038
|
||||||
|
public sealed class CanonicalJsonShapeAnalyzer : DiagnosticAnalyzer
|
||||||
|
{
|
||||||
|
private const string DIAGNOSTIC_ID = Identifier.CANONICAL_JSON_SHAPE_ANALYZER;
|
||||||
|
|
||||||
|
private const string ATTRIBUTE_NAME = "CanonicalJsonShapeAttribute";
|
||||||
|
|
||||||
|
private const string PROPERTY_NAME_ATTRIBUTE = "JsonPropertyNameAttribute";
|
||||||
|
|
||||||
|
private const string IGNORE_ATTRIBUTE = "JsonIgnoreAttribute";
|
||||||
|
|
||||||
|
private const string CONDITION_ARGUMENT = "Condition";
|
||||||
|
|
||||||
|
private const string DEFAULT_CONDITION = "Always";
|
||||||
|
|
||||||
|
private const string TITLE = "Canonical JSON shape must match its declared signature";
|
||||||
|
|
||||||
|
private const string MESSAGE_FORMAT = "The JSON shape of '{0}' no longer matches its declared signature. Data that was hashed with the previous shape stops being readable, so update the attribute to \"{1}\" only once that is acceptable.";
|
||||||
|
|
||||||
|
private const string DESCRIPTION = "The serialized form of this type is hashed into stored data. Adding, removing, renaming, or retyping a property changes those bytes and makes previously stored data fail its integrity check, which surfaces as unreadable data rather than as an error. The declared signature exists so that such a change cannot pass unnoticed.";
|
||||||
|
|
||||||
|
private const string CATEGORY = "Usage";
|
||||||
|
|
||||||
|
private static readonly DiagnosticDescriptor RULE = new(DIAGNOSTIC_ID, TITLE, MESSAGE_FORMAT, CATEGORY, DiagnosticSeverity.Error, isEnabledByDefault: true, description: DESCRIPTION);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders property types the way they are written in the source, including nullable annotations.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly SymbolDisplayFormat TYPE_FORMAT = SymbolDisplayFormat.MinimallyQualifiedFormat.WithMiscellaneousOptions(
|
||||||
|
SymbolDisplayMiscellaneousOptions.UseSpecialTypes | SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);
|
||||||
|
|
||||||
|
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [RULE];
|
||||||
|
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||||
|
context.EnableConcurrentExecution();
|
||||||
|
context.RegisterSymbolAction(AnalyzeType, SymbolKind.NamedType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AnalyzeType(SymbolAnalysisContext context)
|
||||||
|
{
|
||||||
|
var type = (INamedTypeSymbol)context.Symbol;
|
||||||
|
var declaration = type.GetAttributes().FirstOrDefault(attribute => attribute.AttributeClass?.Name == ATTRIBUTE_NAME);
|
||||||
|
if (declaration is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var declared = declaration.ConstructorArguments.Length > 0 ? declaration.ConstructorArguments[0].Value as string : null;
|
||||||
|
var actual = ComputeSignature(type);
|
||||||
|
if (declared == actual)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var location = declaration.ApplicationSyntaxReference?.GetSyntax(context.CancellationToken).GetLocation() ?? type.Locations.FirstOrDefault();
|
||||||
|
if (location is not null)
|
||||||
|
context.ReportDiagnostic(Diagnostic.Create(RULE, location, type.Name, actual));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Derives the shape signature from everything that changes the serialized bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Entries are ordered by their JSON name rather than by declaration order, because the hashed JSON
|
||||||
|
/// is canonicalized with ordinally sorted properties. Moving a property within its type therefore
|
||||||
|
/// does not change any stored hash, and must not fail the build either.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="type">The type to inspect.</param>
|
||||||
|
/// <returns>The signature of the serialized shape.</returns>
|
||||||
|
private static string ComputeSignature(INamedTypeSymbol type)
|
||||||
|
{
|
||||||
|
List<string> entries = [];
|
||||||
|
foreach (var property in type.GetMembers().OfType<IPropertySymbol>())
|
||||||
|
{
|
||||||
|
if (property.IsStatic || property.IsIndexer || property.GetMethod is null || property.DeclaredAccessibility != Accessibility.Public)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
entries.Add($"{JsonName(property)}|{property.Type.ToDisplayString(TYPE_FORMAT)}|{IgnoreMarker(property)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.Sort(System.StringComparer.Ordinal);
|
||||||
|
return Fnv1A(string.Join("\n", entries));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the JSON name a property is written with.
|
||||||
|
/// </summary>
|
||||||
|
private static string JsonName(IPropertySymbol property)
|
||||||
|
{
|
||||||
|
var attribute = property.GetAttributes().FirstOrDefault(candidate => candidate.AttributeClass?.Name == PROPERTY_NAME_ATTRIBUTE);
|
||||||
|
if (attribute is not null && attribute.ConstructorArguments.Length > 0 && attribute.ConstructorArguments[0].Value is string name)
|
||||||
|
return name;
|
||||||
|
|
||||||
|
return property.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ignore behavior of a property, which decides whether it appears at all.
|
||||||
|
/// </summary>
|
||||||
|
private static string IgnoreMarker(IPropertySymbol property)
|
||||||
|
{
|
||||||
|
var attribute = property.GetAttributes().FirstOrDefault(candidate => candidate.AttributeClass?.Name == IGNORE_ATTRIBUTE);
|
||||||
|
if (attribute is null)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
foreach (var argument in attribute.NamedArguments)
|
||||||
|
{
|
||||||
|
if (argument.Key != CONDITION_ARGUMENT)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var rendered = argument.Value.ToCSharpString();
|
||||||
|
var separator = rendered.LastIndexOf('.');
|
||||||
|
return separator < 0 ? rendered : rendered.Substring(separator + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEFAULT_CONDITION;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes a stable 32-bit FNV-1a hash, rendered as eight lowercase hexadecimal digits.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The built-in string hash is randomized per process and would produce a different signature on
|
||||||
|
/// every build, so the signature is computed explicitly here.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="value">The text to hash.</param>
|
||||||
|
/// <returns>The signature text.</returns>
|
||||||
|
private static string Fnv1A(string value)
|
||||||
|
{
|
||||||
|
var hash = 2166136261u;
|
||||||
|
foreach (var character in value)
|
||||||
|
{
|
||||||
|
hash ^= character;
|
||||||
|
hash *= 16777619u;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash.ToString("x8");
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user