Skip to content

fix(dotnet): make InternalsVisibleTo work under strong-naming - #2296

Open
1aifanatic wants to merge 1 commit into
ag-ui-protocol:mainfrom
1aifanatic:fix/dotnet-ivt-under-strong-naming
Open

fix(dotnet): make InternalsVisibleTo work under strong-naming#2296
1aifanatic wants to merge 1 commit into
ag-ui-protocol:mainfrom
1aifanatic:fix/dotnet-ivt-under-strong-naming

Conversation

@1aifanatic

Copy link
Copy Markdown

Fixes #2165

I hit this the first time I ran the .NET tests, before I'd read the issue — dotnet test tests/AGUI.Server.UnitTests on a clean main gave me a wall of CS0122 errors pointing at test files, with nothing suggesting the real cause was assembly signing.

Root cause

Two things combine, and the issue only named one of them:

  1. InternalsVisibleTo was gated off under signing. Each ItemGroup carried Condition="'$(SignAssembly)' != 'true'", so a default build emitted no friend attribute at all.
  2. The test assemblies were never signed. SignAssembly / AssemblyOriginatorKeyFile lived inside the '$(IsPackable)' == 'true' PropertyGroup, so only the five product assemblies were strong-named. A strong-named assembly can only grant InternalsVisibleTo to another strong-named assembly, so just deleting the Condition (option 1 as literally worded) would have traded CS0122 for CS1726.

Fix — option 1, done properly

  • Signing moves out of the IsPackable group so every project is signed with AGUI.snk, including the test projects that consume internals.

  • $(PublicKey) is set to the public key of AGUI.snk. The SDK appends PublicKey=$(PublicKey) to any InternalsVisibleTo item without explicit Key metadata — this is the built-in behaviour the issue guessed at, in Microsoft.NET.GenerateAssemblyInfo.targets:

    <_Parameter1 Condition="'%(InternalsVisibleTo.Key)' != ''">%(InternalsVisibleTo.Identity), PublicKey=%(InternalsVisibleTo.Key)</_Parameter1>
    <_Parameter1 Condition="'%(InternalsVisibleTo.Key)' == '' and '$(PublicKey)' != ''">%(InternalsVisibleTo.Identity), PublicKey=$(PublicKey)</_Parameter1>
    <_Parameter1 Condition="'%(InternalsVisibleTo.Key)' == '' and '$(PublicKey)' == ''">%(InternalsVisibleTo.Identity)</_Parameter1>

    The SDK does not derive $(PublicKey) from the .snk on its own — it has to be set, which is what was missing.

  • $(PublicKey) is conditioned on '$(SignAssembly)' == 'true', so -p:SignAssembly=false still lands on the third branch above: attribute emitted with no key, everything unsigned, exactly as today. The escape hatch keeps working.

  • The four Condition="'$(SignAssembly)' != 'true'" gates are removed.

Since the flag is no longer needed, both workflows drop it (13 call sites). That's deliberate rather than cosmetic: with the flag, CI never built the configuration anyone actually ships or develops against — it only ever tested the unsigned one. Now CI, local dotnet test, and the release build all agree.

Verification

dotnet test tests/<Project> -c Release with no flag — the command that fails on main:

Project net8.0 net9.0 net10.0
AGUI.Abstractions.UnitTests 185 ✅ 185 ✅ 185 ✅
AGUI.Client.UnitTests 114 ✅ 114 ✅ 114 ✅
AGUI.Server.UnitTests 114 ✅ 114 ✅ 114 ✅
AGUI.Protobuf.UnitTests 53 ✅ 53 ✅ 53 ✅
AGUI.Formatting.UnitTests 8 ✅ 8 ✅ 8 ✅

Plus AGUI.Hosting.AspNetCore.IntegrationTests — 117 ✅ (net10.0).

Also checked:

  • The escape hatch still works-c Release -p:SignAssembly=false passes (the old CI command).
  • Release signing is unchanged. After a clean rebuild, every packable assembly still carries public key token d5950b1d09108385 on net10.0 and netstandard2.0, and the newly-signed test assemblies carry the same token.
  • All five TFMs build clean (net10.0;net9.0;net8.0;netstandard2.0;net472), 0 warnings under TreatWarningsAsErrors.
  • CrossLanguage.TestServer (a friend of AGUI.Protobuf) builds unflagged. I couldn't run AGUI.CrossLanguage.IntegrationTests locally — it needs the TypeScript fake-agent server on :8092, which is a pnpm/vitest setup I didn't stand up — but that's a runtime dependency, not a compile one, and the project compiles fine without the flag.

Note on scope

I went with option 1 rather than 2 or 3 because it's the only one that leaves a single configuration: option 2 would make Debug and Release produce differently-identified assemblies, and option 3 documents a papercut instead of removing it. If you'd rather keep -p:SignAssembly=false in CI so the unsigned path stays covered, I'm happy to restore those lines and keep only the Directory.Build.props + .csproj changes.

`dotnet test tests/<Project>` — the command AGENTS.md documents — failed to
compile on a clean checkout with a wall of CS0122 "inaccessible due to its
protection level" errors. Only `-p:SignAssembly=false` made it work, and
nothing in the error output pointed at signing.

Two things combined to cause it:

- `SignAssembly`/`AssemblyOriginatorKeyFile` lived in the
  `'$(IsPackable)' == 'true'` PropertyGroup, so the product assemblies were
  strong-named but the test assemblies were not. A strong-named assembly can
  only grant InternalsVisibleTo to another strong-named assembly.
- Each `InternalsVisibleTo` ItemGroup was gated on
  `'$(SignAssembly)' != 'true'`, so a default build emitted no friend
  attribute at all.

Signing now applies to every project, and the public key of AGUI.snk is set
as `$(PublicKey)`. The .NET SDK appends `PublicKey=$(PublicKey)` to any
InternalsVisibleTo item that carries no explicit Key metadata
(Microsoft.NET.GenerateAssemblyInfo.targets), so friend references stay valid
while signing is on. `$(PublicKey)` is conditioned on `SignAssembly`, so
`-p:SignAssembly=false` still emits the attribute without a key and unsigned
builds keep working.

With the flag no longer needed, the CI workflows drop it and now exercise the
same signed configuration developers build locally.

Verified: `dotnet test tests/<Project> -c Release` (no flag) passes for all
five unit-test projects across net8.0/net9.0/net10.0 and for the ASP.NET Core
integration tests, and the packable assemblies keep public key token
d5950b1d09108385 on every target framework after a clean rebuild.

Fixes ag-ui-protocol#2165
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.

[DX] .NET unit tests fail to build locally without -p:SignAssembly=false (InternalsVisibleTo dropped under signing)

1 participant