Skip to content

Fix MEAI001 leak from experimental RequiresConfirmation in source-generated AIContent contexts - #7659

Merged
jeffhandley merged 1 commit into
dotnet:mainfrom
jeffhandley:jeffhandley/fix-ai-json-experimental-leak
Jul 27, 2026
Merged

Fix MEAI001 leak from experimental RequiresConfirmation in source-generated AIContent contexts#7659
jeffhandley merged 1 commit into
dotnet:mainfrom
jeffhandley:jeffhandley/fix-ai-json-experimental-leak

Conversation

@jeffhandley

@jeffhandley jeffhandley commented Jul 27, 2026

Copy link
Copy Markdown
Member

Tracks #7658

Problem

ToolApprovalRequestContent.RequiresConfirmation is [Experimental(MEAI001)], while ToolApprovalRequestContent is a stable, statically registered [JsonDerivedType] of the [JsonPolymorphic] AIContent and InputRequestContent hierarchies. This regression was introduced in 10.8.0 by Add ToolApprovalRequestContent.RequiresConfirmation (#7549).

As a result, System.Text.Json source generation emits the experimental member into the JSON metadata for any consumer context that reaches AIContent, even when the consumer never uses approval APIs:

[JsonSerializable(typeof(List<AIContent>))]
internal partial class JsonContext : JsonSerializerContext;

The build emits MEAI001 from source-generated code. Consumers are then forced into a project-wide suppression as the only practical remedy, but the suppression does not remove the member from their generated contract: their externally exposed JSON payloads still include the experimental requiresConfirmation field without an approval-feature opt-in.

Fix

ToolApprovalRequestContent is stable; only RequiresConfirmation is experimental. Keep the type in the static polymorphic contract, but gate its serialization at the member level using the established pattern used by UsageDetails token counts and HostedFileContent:

[Experimental(...)]
[JsonIgnore]
public bool RequiresConfirmation
{
    get => RequiresConfirmationCore;
    set => RequiresConfirmationCore = value;
}

[JsonInclude]
[JsonPropertyName("requiresConfirmation")]
internal bool RequiresConfirmationCore { get; set; } = true;

The internal member remains available to the package-owned default serialization path, so persisted conversations continue to round-trip requiresConfirmation. Consumer source-generated contracts omit the experimental field, and direct use of the public property continues to require MEAI001 opt-in.

The two Microsoft.Extensions.AI.Evaluation.Reporting projects had gained <NoWarn>$(NoWarn);MEAI001</NoWarn> alongside #7549 solely because of this leak. The fix removes both suppressions; their clean builds demonstrate that they were leak-driven. Abstractions and AI retain their suppressions because they legitimately use experimental APIs directly.

Regression coverage

Adds a no-suppression consumer-boundary guard in Microsoft.Extensions.AI.Stabilization.Tests:

  • AIContentSerializationRegressionContext.cs source-generates List<AIContent>. Any experimental member leaking into the contract produces MEAI001 at compile time.
  • AIContentSerializationRegressionTests.cs confirms stable List<AIContent> round-trips through that generated context.
  • ToolApprovalRequestContent tests verify RequiresConfirmation still round-trips as both true and false through AIJsonUtilities.DefaultOptions and AIContent polymorphism.

Validation

  • Microsoft.Extensions.AI.Stabilization.Tests: 90/90 pass across net472, net8.0, net9.0, and net10.0 without an MEAI001 suppression.
  • Microsoft.Extensions.AI.Abstractions.Tests: 1634 pass; the intended persistence round-trip remains intact.
  • Negative proof: restoring the original public serialized property produces MEAI001 in the generated AIContentSerializationRegressionContext.ToolApprovalRequestContent.g.cs across all four target frameworks.
Microsoft Reviewers: Open in CodeFlow

…erated AIContent contexts

ToolApprovalRequestContent.RequiresConfirmation is [Experimental(MEAI001)] but the
type is a stable, statically-registered [JsonDerivedType] of the [JsonPolymorphic]
AIContent. System.Text.Json's source generator therefore emits the experimental member
into the JSON metadata of any consumer whose JsonSerializerContext includes AIContent
(e.g. List<AIContent>), forcing that consumer to suppress MEAI001 even when it never
uses approval APIs.

Route serialization through a non-experimental internal member: the public property is
now [JsonIgnore] and delegates to internal RequiresConfirmationCore, which carries
[JsonInclude] and [JsonPropertyName("requiresConfirmation")] so the value still
round-trips through the package's default serialization options. This mirrors the
existing pattern on UsageDetails token counts and HostedFileContent.

Also removes the now-unnecessary MEAI001 suppressions from the two Evaluation.Reporting
projects, which had gained them solely because of this leak (PR dotnet#7549).

Adds a no-suppression, MEAI001-as-error regression guard in the Stabilization tests: a
source-generated context over List<AIContent> that fails to compile if any experimental
member leaks back into consumer metadata.

Fixes dotnet#7658

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 27, 2026 11:07
@jeffhandley
jeffhandley requested review from a team as code owners July 27, 2026 11:07
@jeffhandley
jeffhandley requested a review from jozkee July 27, 2026 11:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a regression where the experimental ToolApprovalRequestContent.RequiresConfirmation member leaked into consumers’ System.Text.Json source-generated AIContent contracts (triggering MEAI001) by gating serialization at the member level while preserving round-tripping through the library’s default serialization path.

Changes:

  • Mark RequiresConfirmation as [JsonIgnore] and route JSON through an internal RequiresConfirmationCore property annotated with [JsonInclude]/[JsonPropertyName].
  • Add a stabilization test JsonSerializerContext rooted at List<AIContent> plus a runtime round-trip test to ensure the generated context is functional.
  • Remove now-unnecessary MEAI001 suppressions from the two Evaluation.Reporting projects.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
test/Libraries/Microsoft.Extensions.AI.Stabilization.Tests/AIContentSerializationRegressionTests.cs Runtime round-trip test to validate the source-generated List<AIContent> context works.
test/Libraries/Microsoft.Extensions.AI.Stabilization.Tests/AIContentSerializationRegressionContext.cs Compile-time guard context to prevent experimental-member leakage into consumer source generation.
src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Microsoft.Extensions.AI.Evaluation.Reporting.csproj Removes MEAI001 suppression that was only needed due to the leak.
src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Microsoft.Extensions.AI.Evaluation.Reporting.Azure.csproj Removes MEAI001 suppression that was only needed due to the leak.
src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs Implements the [JsonIgnore] + internal *Core serialization pattern to stop source-gen leakage while preserving default-option round-trip behavior.

@jozkee jozkee left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you.

@jeffhandley
jeffhandley merged commit afda3f7 into dotnet:main Jul 27, 2026
7 checks passed
jeffhandley added a commit that referenced this pull request Jul 27, 2026
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This was referenced Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants