Skip to content

Ship the storage-type alias props in build/, not buildTransitive/ [patch] - #260

Merged
matt-edmondson merged 1 commit into
mainfrom
claude/alias-props-build-folder-244
Sep 21, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
claude/alias-props-build-folder-244

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #244

The change

The four Semantics.Quantities.{Double,Float,Decimal,Precise} packages now pack their alias props into build/ instead of buildTransitive/. This is suggested fix 1 in the issue — the one that removes the failure mode rather than diagnosing it.

buildTransitive/ is by definition the folder whose contents flow past the referencing project to everything downstream of it. The props are ~220 project-wide global usings keyed on the bare type name, so two storage types reaching one project define every alias twice and the compile dies with one CS1537 per quantity against GlobalUsings.g.cs.

That is what made the collision reachable from a project that references no alias package at all: the documented rule, one alias package per project, was satisfied by every project in the issue's repro and the build still failed. build/ binds the aliases in the project that declares the reference and nowhere else, which is what the rule already says.

Establishing what motivated buildTransitive first

The triage comment asked for this before committing to fix 1. Nothing depended on the transitive flow:

  • No project in this repository consumes an alias package — Semantics.Test and Semantics.Benchmarks both reference ktsu.PreciseNumber directly, each with a comment saying they do so rather than picking it up through Semantics.Quantities.Precise, "which ships only props".
  • Generate-AliasProps.ps1's own docstring described the intent as "a project that references the package can write Mass" — build/ semantics, written against the buildTransitive/ folder.
  • Every README already documents the binding as per-project.

So buildTransitive/ looks like the conventional folder rather than a chosen behaviour, and fix 2 (the self-naming SEM010 error) is not needed: its stated precondition, "if buildTransitive has to stay", does not hold. A single project that directly references two alias packages is still a CS1537 pile — but that project violates the documented rule itself, rather than inheriting a binding it never asked for. Happy to add the guard as a follow-up if you want that case named too.

Verification

End to end, against the exact repro in the issue (Foo.Storage.Double + Foo.Storage.Float, each referencing one alias package, and Foo.App project-referencing both and referencing no alias package):

Packages built from dotnet build Foo.App
props in buildTransitive/ (today's main) fails, 440 CS1537 lines
props in build/ (this branch) succeeds; Foo.App runs and prints Mass\1[System.Double]andMass`1[System.Single]` — each wrapper keeps its own binding

Also:

  • dotnet build Semantics.sln -c Release — 0 warnings, 0 errors.
  • Semantics.Test on net10.0 — 1283 tests, 1275 passed, 8 skipped (Windows-only), 0 failed.
  • The packed .nupkg for Double and Float each contain exactly build/ktsu.Semantics.Quantities.<T>.props.
  • No generated-source drift after a Release build, so the verify-generated gate stays clean. The generator script's output is byte-identical; only its destination directory changed, which is why the props show as pure renames in the diff.

Test

Semantics.Test/Quantities/AliasPropsPackagingTests.cs, following the precedent of GeneratorProjectReferenceTests — a condition cheap to state and cheap to check, so it is checked rather than left to a comment. Two tests assert each alias project packs exactly one props file, into build/, naming the file that exists on disk.

Confirmed failing without the fix: reverting the folder move and the PackagePath while keeping the tests produces 2 failures naming all four packages; restoring the fix returns the suite to 1283/0.

The folder is one word in a PackagePath that no build in this repository exercises — nothing here consumes the alias packages as packages — and is only wrong once a consumer composes two of them. A regression should cost a test, not a release.

Docs

Per suggested fix 3, the per-project scope is now documented where a consumer will meet it — the four package READMEs, the four package <Description>s, and CLAUDE.md — including that PrivateAssets="all" was the workaround for the old layout and is no longer needed. scripts/Generate-AliasProps.ps1 and the verify-generated.yml header say why the folder is build/, so the next person does not "correct" it back.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CGHNqRPzdBvCeegruQ11CG


Generated by Claude Code

…tch]

buildTransitive/ is by definition the folder whose contents flow past the
project that referenced the package to everything downstream of it. The alias
props are 220 project-wide global usings keyed on the bare type name, so two
storage types reaching one project define every alias twice and the compile
dies with one CS1537 per quantity against GlobalUsings.g.cs.

That made the collision reachable from a project that references no alias
package at all. An application assembled from one project per storage type --
which is the shape the four packages exist to enable -- satisfies "one alias
package per project" in every project it contains, and still does not build.
Nothing in the 220 x (n-1) errors names a package, and the file they point at
is one the author never wrote.

build/ binds the aliases in the project that declares the reference and
nowhere else, which is what the documented rule already says. The transitive
explosion then cannot happen, rather than being diagnosed after the fact, and
the PrivateAssets="all" workaround stops being something a consumer has to
know in advance.

Verified end to end against the issue's repro -- two wrapper projects, one per
storage type, and an app that project-references both. Packed from this branch
it builds and each wrapper keeps its own binding; packed with the props back
in buildTransitive/ it fails with the CS1537 wall.

AliasPropsPackagingTests holds the folder, since it is one word in a
PackagePath that no build in this repository exercises -- nothing here consumes
the alias packages as packages -- and is only wrong once a consumer composes
two of them.

Fixes #244

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CGHNqRPzdBvCeegruQ11CG

matt-edmondson commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor Author

CI: github-advanced-security failed, and it is not this PR's

Run 35594651241 — the dynamic/agents/github-advanced-security code-scanning agent, driven by the GitHub App rather than by a workflow in this repo.

It failed 38 seconds in, at session creation, before reading the diff:

Error creating PR review request: SessionModelError: You have exceeded your monthly quota
  statusCode: 402, errorCode: "quota"

The log shows it got as far as fetching the PR metadata and building a prompt, then got HTTP 402 from api.business.githubcopilot.com and exited 1. It never analysed a file. Note also that its FileExclusionPatterns exclude *.cs, *.csproj, *.md, *.sln and .github/workflows/*.yml — which is every file this PR touches — so even with quota available it would have had nothing in scope here.

No fix exists to port. This is the account's monthly Copilot/GHAS quota, not a repository or branch condition: it will fail identically on every PR in the org until the quota resets or is raised. Nothing I can change in this branch affects it, and I'm not going to widen the PR trying.

I tried to re-run the job once to confirm it reproduces, and GitHub refused it — 403 This workflow run cannot be retried, which is expected for an App-driven dynamic/ run. So I have no way to retry it from here; it needs the quota raised or reset, or the check made non-blocking, on your side.

The rest of CI is still running on 7d87d5c — .NET Workflow, Verify Generated Files, and the two CodeQL runs. I'll keep watching and will act on anything that is this PR's.


Generated by Claude Code

@sonarqubecloud

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit 2ccb6b4 into main Sep 21, 2026
12 of 13 checks passed
@matt-edmondson
matt-edmondson deleted the claude/alias-props-build-folder-244 branch September 21, 2026 12:19
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.

Storage-type alias packages are not composable: two in one dependency graph produce 220 duplicate global usings

1 participant