Skip to content

[StaticWebAssets] Add agent triage guidance + reasoning eval for endpoint-manifest collisions#55035

Draft
PureWeen wants to merge 1 commit into
dotnet:mainfrom
PureWeen:swa-agent-triage-knowledge
Draft

[StaticWebAssets] Add agent triage guidance + reasoning eval for endpoint-manifest collisions#55035
PureWeen wants to merge 1 commit into
dotnet:mainfrom
PureWeen:swa-agent-triage-knowledge

Conversation

@PureWeen

Copy link
Copy Markdown
Member

What this adds

Triage knowledge + a reasoning eval for the Static Web Assets SDK failure mode
InvalidOperationException: Sequence contains more than one element at
ChooseNearestAssetKind(...).SingleOrDefault() in GenerateStaticWebAssetEndpointsManifest
(and the identical pattern in GenerateStaticWebAssetsDevelopmentManifest).

This is documentation, code comments, and an eval only — no functional/runtime change. Six files,
all under src/StaticWebAssetsSdk/:

  • AGENTS.md — a "Triage Heuristic": the crash site is never the fix, and "blame the producer" is only
    half-right. Localize where the surplus asset is born and prevent it there. The durable fix makes the
    producing package's fallback conditional so exactly one asset ever lands on the route; an SDK-side
    carry-forward that re-applies the build-time group resolution at publish is an acceptable-but-superseded
    alternative. Do not soften the throwing task, do not retype the package asset as Framework while
    still emitting it unconditionally, and do not add a consumer-scoped Remove in the publish targets
    (the publish group filter is consumer-scoped and structurally cannot resolve a package-owned group).
  • Architecture.md — documents deferred groups, why the build manifest retains all variants, and the
    conditional-package-fallback fix vs. the superseded SDK carry-forward.
  • Code comments at StaticWebAsset.ChooseNearestAssetKind and the SingleOrDefault throw site, pointing
    readers at the durable fix.
  • evals/ — a reasoning eval (corrected fixture from the real failing binlog + a tiered rubric) that
    measures whether this guidance moves a fresh agent past the symptom-site, unconditional-package-retype,
    consumer-scoped-Remove, and downstream-de-duplication answers to the conditional package fix.

Background: the real case (#54779)

A .NET MAUI Blazor Hybrid app referencing Microsoft.AspNetCore.Components.WebView + a Razor Class Library
with JS modules built fine but failed to publish, because the package's fallback
_framework/blazor.modules.json survived alongside the app's own modules manifest — two assets on one route.

The fix that landed is in the package, not the SDK: dotnet/aspnetcore#67375 (merged; backport
dotnet/aspnetcore#67401) makes the WebView blazor.modules.json fallback conditional (materialized
only when the consumer contributes no JS modules of its own). A candidate SDK-side carry-forward,
#54941, was built and empirically verified to work but was not merged (it compensates
downstream rather than preventing the second asset).

Why this lives in dotnet/sdk (even though the fix shipped in aspnetcore)

  • The throwing task (GenerateStaticWebAssetEndpointsManifest) and ChooseNearestAssetKind exist only
    in dotnet/sdk — the code comments can't live anywhere else.
  • The crash surfaces in SDK code; a future engineer debugging it starts here. The eval fixture is a
    dotnet/sdk binlog.
  • This failure mode is general across all Static Web Assets scenarios (Blazor WASM, Razor, MAUI Hybrid);
    WebView/blazor.modules.json is just the worked example. General triage guidance belongs in the general
    (SDK) location and points to the package-side fix.

The fix's home repo (aspnetcore) is independently protected by the regression test suite added in #67375.

Verification

The conclusion the eval is calibrated to was empirically verified: on a genuinely pre-fix SDK, swapping only
the WebView package between runs reproduces the crash with the public package and resolves it (one asset, real
content) with the conditional-fallback package — including on a real dotnet new maui-blazor app against the
official preview.6 toolchain, with no SDK change.


Draft for review — feedback welcome on framing, repo placement, and the eval rubric.

…oint-manifest collisions

When GenerateStaticWebAssetEndpointsManifest throws "Sequence contains more than one
element" (ChooseNearestAssetKind(...).SingleOrDefault()), the opaque crash invites a
symptom-site "fix" that softens the throwing task, or an over-correction that retypes the
producing package's asset as a Framework asset while still emitting it unconditionally.
Both are wrong: the first masks the defect, the second still leaves two assets on the route.

The durable fix stops the second asset from being emitted at all. The producing package's
build targets run inside the consumer's build, so they can gate a fallback on whether the
consumer already supplies its own asset (e.g. @(_ExistingBuildJSModules) == '') and
materialize it only when there is none - so exactly one asset ever lands on the route and
the collision cannot form, with no SDK change. This is what landed for dotnet#54779:
dotnet/aspnetcore#67375 (merged; backport dotnet/aspnetcore#67401) makes the
Microsoft.AspNetCore.Components.WebView blazor.modules.json fallback conditional. An SDK-side
carry-forward that re-applies the build-time group resolution at publish (dotnet#54941)
was built and empirically verified to work, but it de-duplicates two assets downstream rather
than preventing the second one, and was not merged (superseded).

This change installs the missing knowledge and a way to measure it:

- AGENTS.md: a "Triage Heuristic" - the crash site is never the fix, and "blame the producer"
  is only half-right. Localize where the surplus asset is *born* and prevent it there. The
  preferred fix makes the package fallback conditional (one asset); the SDK carry-forward is an
  acceptable-but-superseded alternative (two assets de-duplicated). Do NOT soften the task, do
  NOT retype the package asset as Framework while still emitting it unconditionally, and do NOT
  add a consumer-scoped Remove in the publish targets (the publish group filter is
  consumer-scoped and structurally cannot resolve a package-owned group).
- Architecture.md: document deferred groups, the build manifest retaining all variants, and the
  conditional-package-fallback fix vs. the superseded SDK carry-forward.
- Code comments at ChooseNearestAssetKind and the SingleOrDefault throw site, pointing at the
  conditional package fix (dotnet/aspnetcore#67375) as durable and dotnet#54941 as superseded.
- evals/: a reasoning eval (corrected fixture + tiered v3 rubric) that measures whether guidance
  moves a fresh agent past the symptom-site, unconditional-package-retype, consumer-scoped-Remove,
  and downstream-de-duplication answers to the conditional package fix. Includes empirical
  verification (synthetic harness + a real dotnet new maui-blazor app on the genuine preview.6
  SDK), the Scenario D edge, and the methodology lessons (ground data in the real binlog; don't
  calibrate against an unsettled investigation - the correct answer moved three times).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@lewing lewing requested a review from javiercn June 26, 2026 19:11
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.

1 participant