Skip to content

Rules updates - #3

Merged
kieronlanning merged 11 commits into
mainfrom
rules-updates
Oct 7, 2026
Merged

kieronlanning merged 11 commits into
mainfrom
rules-updates

Conversation

@kieronlanning

Copy link
Copy Markdown
Contributor

No description provided.

…rvability

Consuming these packages forced preview features on the whole consuming solution.
EnablePreviewFeatures was set repo-wide, emitting [assembly: RequiresPreviewFeatures] into all four
shipped assemblies. Because that attribute is assembly-wide, CA2252 - an error by default - fired on
every member a consumer touched, including Result<TValue, TError>, which uses no union feature. An
application could not adopt the result type at all without enabling preview features solution-wide,
so partial adoption was impossible.

The property was unnecessary. A union declaration is a language feature needing only
LangVersion=preview, and nothing here uses a runtime API annotated [RequiresPreviewFeatures]. The
requirement is now scoped per-project: using the result types and combinators needs nothing special,
and declaring your own [GenerateResult] union needs LangVersion=preview in that project. Both paths
are verified against the packed packages, the first with TreatWarningsAsErrors.

All four packages now multi-target net10.0 and net11.0, so consumers choose their runtime rather than
being forced onto .NET 11. .NET 8 and 9 are deliberately not targeted - both are near end of support.
Unions still require .NET 11, which is a compiler constraint rather than a choice: a union
declaration needs System.Runtime.CompilerServices.IUnion and UnionAttribute, neither of which exists
before .NET 11. Everything else works on net10.0. The HTTP mapper's union unwrapping compiles only
for net11.0; on net10.0 an error is its own single case, the answer the union path already gives for
a non-union error. Runtime async is likewise scoped to net11.0 - on net10.0 Features=runtime-async=on
lowers await to AsyncHelpers, which is evaluation-only and fails the build with SYSLIB5007.

The ASP.NET Core success path no longer erases the value's type. DefaultResultsHttpMapper serialized
through IResultValue.SuccessValue, which is object?, so TypedResults.Ok bound Ok<object>. Under
trimming or Native AOT that leaves the serializer with no JsonTypeInfo to resolve, and it erased the
response type ASP.NET Core infers for OpenAPI. The value now reaches TypedResults with its static
type through a new IResultValueVisitor<TState, TReturn> and IResultValue.AcceptSuccess double
dispatch - no reflection. All four packages are marked IsAotCompatible and verified clean under both
the trim and AOT analyzers, independently confirming the "no reflection, no dynamic, no runtime type
discovery" guarantee.

Observability: metrics under the meter Purview.Results.AspNetCore -
purview.results.failures.mapped, .unmapped and purview.results.uninitialized. The design calls an
unmapped failure a host mapping gap rather than a domain outcome, but the only signal was a log
message, so answering "how often, and for which error" meant scraping log text. Each measurement
carries a purview.results.error_type tag, and the failure paths add the same tag to Activity.Current,
enriching the request span ASP.NET Core already started rather than creating one. Every log statement
now has a stable EventId via source-generated LoggerMessage. No telemetry package dependency is
added: Meter and Activity come from the base class library.

The error type is always present in telemetry, because metrics and traces stay inside your own
infrastructure. Disclosing it in the HTTP response is now opt-in via
IncludeErrorTypeInProblemDetails, defaulting to off - previously Type.FullName went to every client
on the unmapped-failure path with no way to disable it.

ResultsHttpOptions snapshots its mappings into a FrozenDictionary and an IReadOnlyList on first read.
It previously held a plain Dictionary and List, writable through public methods for the process
lifetime while the singleton mapper read them per request.

Fixed: an array-typed union case crashed the generator and discarded every other union's output. The
discovery code explicitly handles IArrayTypeSymbol when checking accessibility, then cast the case to
INamedTypeSymbol - which an array is not - throwing InvalidCastException. With no catch-all the
compiler reported CS8785 and dropped all generated source for the compilation: every AsFailure helper
and union factory vanished, producing a cascade of unrelated CS1061/CS0103 errors with no indication
of the cause, and AD0001 in the IDE. A single malformed union anywhere in a solution did this. Now
reported as RSG1009 and skipped. A generator catch-all reports RSG9000 against the compilation rather
than letting an unexpected failure escape; OperationCanceledException is rethrown, because the IDE
cancels generation on every keystroke.

Async combinators now await with ConfigureAwait(false), so they no longer resume on a captured
synchronization context.

All eleven diagnostics (RSG1000-RSG1009, RSG9000) moved into a new AnalyzerReleases.Shipped.md under
Release 1.0.0 - there was no Shipped.md at all, so the catalogue claimed nothing had ever shipped.
RSG2000 is deliberately absent from both files: suppression descriptors are not release-tracked.

Docs: thread safety and observability recorded in Guarantees and Limitations, including that a result
is safe by value but not in a shared mutable field, because a torn read of the three-field struct
elides the null check [MemberNotNullWhen] promised.

BREAKING CHANGE: IResultValue gained AcceptSuccess<TState, TReturn>. Both in-box result types
implement it; external implementations are not expected.
…rk 1.0.0

Moves from Purview.BuildSdk 1.0.2.2 and Purview.SourceGeneratorFramework
1.0.0-prerelease.54 to the first stable releases of both.

Target frameworks are now selected rather than listed. net10.0;net11.0 is
exactly the SDK's 'Supported' set - everything still in support, with .NET 8
and 9 deliberately excluded for the reason already documented here - so
Directory.Build.props selects the set instead of spelling the list out. The
resolved list is identical, verified before testing, so no package targets
change.

Fixed warnings the previous SDK was hiding, all in code added earlier on this
branch: an unnecessary using in DefaultResultsHttpMapper (needed only by the
net11.0-gated union path, so it is now conditional) and in
ResultsHttpOptionsSafetyTests, an unnecessary null-forgiving operator in
ResultT.AcceptSuccess, 'var with target-typed new' in ResultsHttpTelemetry and
AcceptSuccessTests, and two CA1859 returns boxing a concrete result into
IResult - DefaultResultsHttpMapper.Json and ZodValidationProblems.WriteProblem
now return JsonHttpResult<T>, which avoids the box at every call site.

The mirrored .agents files are no longer tracked: the SDK generates
.agents/.gitignore listing the files it mirrors from packages, so a package
upgrade no longer shows up as a local modification.

Tests: 497 passing, 0 failed. Build is clean with no warnings.
@kieronlanning
kieronlanning merged commit fbf0f23 into main Oct 7, 2026
1 check passed
@kieronlanning
kieronlanning deleted the rules-updates branch October 7, 2026 07:13
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