Skip to content

release: 2.3.0 - #176

Merged
CaffeinatedCoder merged 123 commits into
mainfrom
release/2.3.0
Oct 29, 2026
Merged

CaffeinatedCoder merged 123 commits into
mainfrom
release/2.3.0

Conversation

@CaffeinatedCoder

Copy link
Copy Markdown
Owner

Closes #67

Release 2.3.0 into main

The changelog's 2.3.0 section, dated 2026-10-29, is the release notes: async Testing assertions held by CMTK0003, Fault.Cause and WithoutFaultName(), IAsyncEnumerable and Option/Patch traversal, inline value-object schemas, the Add Create code fix, SQL Server oracles, the docs site and walkthrough.

Checked locally on release/2.3.0 before the tag (CONTRIBUTING, "Releasing", step 1):

  • oracles seeds: every seed caught, every baseline passing without its seed, the SQL Server oracles included (exit 0).
  • docs-query due --nuget: no fact due (0 of 34 pinned external).

The last topics were #171 (decision 120), #172 (CMTK0003, #165 and #166) and #175 (the changelog dated, the analyzer release recorded). The tag goes on this PR's merge commit.

🤖 Generated with Claude Code

CaffeinatedCoder and others added 30 commits October 18, 2026 16:29
…abels, milestone, branch and issue link

The repository had 66 pull requests and no issue, milestone or label of its own. It adopts
CodoMetis.ValueRanges' scheme (decision 109):

- issue forms for a bug, an enhancement and an epic, with blank issues off and security reports sent
  to the private advisory form; a pull request template (Closes #n, Problem, Change, Tests);
- labels: one type, a severity on a bug, package/* and area/*, silent, breaking, found-by/prober,
  stacked, epic. The legend is in CONTRIBUTING's "Issues and labels", and AGENTS.md has an Issues section;
- a release is an epic with a milestone of its version; the roadmap stays in design/roadmap.md.

pr-hygiene.yml runs tools/PrHygiene on the event payload of every pull request into main or a release
branch. ValueRanges' check is a bash script; under decision 92 this one is a tool project, BCL only,
with its tests. Besides ValueRanges' rules (one type label, a severity on a bug, a closing reference),
it holds decision 94's branch naming against the type label, the release's milestone, silent only on
a bug, and breaking only into a major. Each rule was proved by deleting it from the tool: 12 seeds,
each caught by the 41 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ReleaseNotesFromChangelog target writes the section under
'## $(Version)' of CHANGELOG.md into every nuspec, as the plain text
nuget.org shows (measured on package pages: it escapes the notes and
links bare URLs, nothing else), with one line linking the whole
changelog. Every package carries the whole section: the areas name
topics, not packages, and every satellite depends on the base package.
A missing or empty section, a relative link, or notes over 35,000
characters fail the pack, naming the file.

PackageReleaseNotesTests holds the packed nuspecs to the section, and
runs the target on a sample changelog of every Markdown shape and of
MSBuild-special text, in LF and CRLF, and on the four it must refuse.

Closes #75

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CodeQL resolved the virtual object.ToString() in Hx.BindText's enum arm to
every override in the database, Sides.Answer.ToString() among them, which reads
JsonException.Path; it then reported exception information reaching the bind
probe's request body (code-scanning alert 1,
cs/information-exposure-through-exception). The value there is always a boxed
enum, so the cast to Enum gives the same text and narrows the call.

Closes #78

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build: track work as labelled issues, and check each PR's labels, milestone, branch and issue link
The branch's previous head carried a failed 'Labels and issue link' run from
the closed PR 79 (its branch was then named chore/), which the release
branch's ruleset counts against PR 80 too. An empty commit gives the PR
check runs of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-enum

refactor: BindText's enum arm calls Enum.ToString (CodeQL alert 1)
…er the problem

A caught exception was lost on the way to a Fault<TFault>: the MapError that turns it into the fault an
endpoint answers dropped it, so a log of the refusal could not say what failed underneath (decision 52).

- Fault<TFault>.Cause, set by Fault.Of(reason, detail, cause) and the constructor of the same shape, and
  Of(reason, detail, cause) on Fault<T1, T2> and Fault<T1, T2, T3>. WithRule and `with` keep it. A null
  cause is refused where one is given.
- Equality and the hash code are the reason's, the detail's and the rule's, written by hand so the cause
  takes no part; ToString prints as 2.2 did.
- AspNetCore: FaultProblem<TFault>.Cause, and IFaultFeature.Cause as a default member, so a host's own
  implementation still builds. The problem's members are the same with a cause as without one.
- READMEs (core, and AspNetCore's "What failed underneath" with compiled samples), changelog, the
  option-result note; the roadmap item goes.
- The C03 seeds are regenerated against the new source. The private Problem() helper stays the
  composed overloads' alone, so each-error-own-set still breaks composed faults only.

Proved by seeding: equality over the cause fails two tests, a dropped cause in ToHttpResult six, and
the cause's message written into the problem five.

Closes #84

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and Patch

Result.CatchAsync gains four ValueTask forms, valued and command, wide and
narrowed, catching as the Task forms do. Each is
[OverloadResolutionPriority(-1)], so an async lambda binds to the Task form
as before; without it the repository's own CatchAsync calls are CS0121, the
ambiguity decision 30 measured. Catch keeps refusing a ValueTask at run time.

Option<T>.TraverseAsync and Patch<T>.TraverseAsync, with token forms, are the
synchronous Traverse with the operation awaited: None, a clear and an absent
field call nothing and check no token.

Decision 110 records both. The completeness tests (null delegate, null task,
asynchronous context, uninitialized result) cover the eight new members.

Closes #88

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t unawaited

Every assertion of CodoMetis.TypeKit.Testing has a form on a Task and on a
ValueTask of what it checks, beside its synchronous form: it awaits the task
and answers as the synchronous form does, the same content back and the same
message. A task that faults propagates its own exception, and a null task is
refused by the parameter's name.

An assertion on a task fails the test only through the task it returns, so
`service.FindAsync(id).ShouldBeSome();` without await passed whatever the
task held, and C# warns only in an async method. CMTK0003 reports such a
statement, through ConfigureAwait and in a lambda's body too. It knows the
assertions by the Testing assembly's identity, as it knows the Generators
assembly, so a consumer's own extension on a pending result stays silent:
every reported call is new in 2.3, and no build that worked before breaks.

The C20 seed whichever-of-its-types is regenerated against the new
FaultAssertions.cs.

Closes #87

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FaultResponse.WithoutFaultName() answers a member without naming it, for
causes a client must not tell apart: a sign-in's unknown user and wrong
password, mapped to 401 and withheld, answer alike without a mapping to one
member at the boundary. The problem has no fault and no rule, and a fault
set member's description no longer stands in as its detail, since it names
the member as surely; the occurrence's own detail is kept. IFaultFeature
gains NameWithheld, a default member, so the host still logs the fault and
sees that the client is not told.

The document's problem schema is shared and keeps fault optional; a
response whose every fault withholds its name ends its description with
"The problem names no fault." The startup warning for two fault types that
write one name counts only the members that write it (Found-By: VA, the
sibling of the problem's fault member).

Decision 111 records it; decision 64's index row points to it. Stacked on
feat/catch-async-valuetask for the record's number.

Seven probe seeds that patch the changed lines are regenerated: C03
shared-status-types, C05 stays-the-errors, C06 description-as-detail and
no-description-no-detail, C08 leave-one-out-fixes, C14 declared-describes-
fault and declared-problem.

Closes #85

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The prober cost more than it paid: a release's `prober seeds --all` built the
probe harness once per seed, 170 seeds at about four minutes, and one change
to the fault responses staled 23 seeds of five probes. The replay, the
footprints, the ledger, the covering arrays and the gate carried the same
weight.

Removed: tools/Prober and its tests, the corpus under tools/probe/ with its
38 probes and 170 seeds, the probes and probe-status jobs of dotnet.yml, the
replay and gate steps of release.yml, .claude/probe-axes.md, the
probe-triage skill, the probe conventions tests, and DocsQuery's check of
the facts the corpus cited. The verified facts and their fixtures stay.

Added, after CodoMetis.ValueRanges (test/oracles/README.md):
- claims.json: the prober's 169 obligations as 150 claims, one per distinct
  sentence, each quoted verbatim, 112 held by a named test class;
- surface-waivers.json: every public member of the run-time libraries is
  claimed or waived; the documents never read for claims are waived as
  unread (#98);
- oracles.json and seeds/: five oracles that already existed
  (JsonParityTests, NullDelegateTests, AsynchronousContextTests,
  NotWireTypeTests, KeyGenerationVariantTests), each with a seed; all five
  caught by `oracles seeds`, about ten seconds each;
- candidates.json and axes.md;
- test/CodoMetis.TypeKit.Oracles.Tests, which holds the catalogue in CI,
  and tools/Oracles, the local `seeds` and `status`.

Decision 112 supersedes decisions 96, 99 to 104 and 108, decision 107 but
for its fixture rule, and decision 97's gate. AGENTS.md, CONTRIBUTING.md and
design/plan.md describe the kit. Stacked on #96 for the decision's number.

Closes #97

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n static fields

CodeAnnotations held seven AttributeConstruction objects in static readonly fields. An
AttributeConstruction belongs to one compilation, and a static field outlives it: at design time,
where the IDE keeps Metalama's process across edits, every compilation that filled the fields stays
reachable. Gael Fraiteur of Metalama found it reviewing the project (metalama/Metalama discussion
2305); metalama/Metalama#2307 will detect the pattern, and is still open.

Each is a static property with an expression body now, so the 78 call sites
(AddAttribute(CodeAnnotations.CompilerGenerated) and the like) are unchanged, and so is the generated
code: the generated-surface snapshot and the woven probes pass as before.

CompileTimeStaticStateTests (conventions) holds every static field of .Generators, and every static
auto-property with storage, to an allowlist of durable types: strings, Type, DiagnosticDefinition,
and arrays, lists and tuples of those. [Template] members are introduced into the target and hold
nothing. With the seven fields restored it fails, naming all seven.

Input class: static storage in compile-time code. The test walks every source of .Generators; the
other static storage there (the DiagnosticDefinitions, TypedConversionMethods, GeneratedMemberNames)
is durable, so there was no sibling to fix.

Defect class CRP: what it costs is memory over an IDE session, with no effect on what is generated.

Closes #90

Defect-Class: CRP
Found-By: OR
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ther assembly's interface

FabricTests gains TestForeignIdentifier, declared in the Generators tests and reaching IValue<Guid>
through IProbeIdentifier, an interface of the probes assembly. ProbeCustomerId covered a derived
interface of the same assembly only. Whatever the fabric selects with, the chain through a type it
did not compile is the one most likely to be missed, and a miss compiles silently.

Seeded with a selection of direct implementations only (DerivedTypesOptions.DirectOnly over the
index of derived types), the theory fails on it and on ProbeCustomerId.

Refs #91

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Metalama.Testing.AspectTesting 2026.1.30 tests the real generators,
internal aspects and TransitiveProjectFabric included, through a project
reference. It runs on xunit v2 and VSTest, so it stays out of the
solution's test run.

- It runs as a project of its own with its own global.json, which keeps
  the SDK pin and drops the test key. `dotnet test` from the spike folder
  passes 4 of 4. From the root, the MTP selection refuses it.
- In a scratch .slnx, one VSTest project makes `dotnet test --solution`
  refuse the whole run, so no other test runs. IsTestProject=false hides
  it from that run, but then VSTest's `dotnet test` also runs nothing and
  exits 0.
- `dotnet vstest` on the built dll runs it from anywhere. CI would need
  one more step, which was not run.
- A seeded defect in a scratch copy of src/ fails the two snapshots
  whose output it changes.
- Plain, validated and closed-set shapes give 235 to 349 lines each.
  They are formatted but fully qualified, so they suit a review per
  member, not per file.
- Reading them showed dead code after the return in every generated
  TryParse(string?): TryParseTemplate's meta.InsertStatement calls are
  still emitted after a compile-time return.

Refs #93

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…osed set

Gael Fraiteur of Metalama suggested showing the generated code for the simple cases (metalama/Metalama
discussion 2305). The root README shows what OrderId gets, and the Generators README what Email and
OrderStatus get, each in a collapsed block beside where the page declares it: the field, the private
constructor, Value, From or TryFrom, FromKnownGood and Revalidate, CompareTo and ToString; for the
closed set the static constructor that names the members, Name and Match.

The blocks are trimmed, not pasted. The woven code is fully qualified, puts attributes on every member
and casts on every returned expression: OrderId alone is about 200 lines, and AspectTesting's expected
files are no shorter (spike, #93). So the check compares reading forms. The samples project sets
MetalamaEmitCompilerTransformedFiles, and GeneratedCodeBlockTests (conventions) holds each block marked
<!-- not compiled: generated code for {file}, trimmed (GeneratedCodeBlockTests) --> to the woven form of
that sample file: every member shown, body included, and every interface of its base list, after both
sides lose attributes, comments, namespace qualifiers, casts and this., and System.String reads as
string. A block may leave members out, and says so in a comment.

Seeded: a From body changed in the root README fails, printing the block's member and the generated
one. A_member_that_differs_is_found holds the comparison to a wrong body, an extra member and an
interface the woven base list lacks.

Closes #92

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every generated TryParse(string?, IFormatProvider?, out TSelf) over a wrapped type with a TryParse
of its own, or over a string, ended in a declaration of __inner and a try/catch (FormatException)
after its return. TryParseTemplate returned from a compile-time if, which ends the run-time body,
but the two meta.InsertStatement calls after it are compile-time code and still ran. Nothing reached
the tail and it changed no behaviour, but it was in every value object that parses, and in the code
the READMEs now show. The AspectTesting spike found it reading its expected output (#93).

The three branches are an if-else chain now, so the InsertStatements run only in the last one: the
strategies without a TryParse of their own (a type converter, a static Parse, a string
constructor), whose body is unchanged.

WovenReachabilityTests (conventions) reads the probes' woven files and fails on any statement after a
return or a throw of the same block, a local function excepted. Before the fix it found 26, one per
value object of the probes that parses; after it, none. A fixture pins what counts.

Input class: the parse strategies. The probes hold each of them (string, enum, Uri, the primitives
and dates, a span-parsable type, NodaTime through its type converter, a static Parse, a string
constructor), and the four final-branch bodies are the same as before. The other template that calls
InsertStatement, JsonConverterWriteTemplate, has no early return.

Closes #100

Defect-Class: GTS
Found-By: SF
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…atics

fix: CodeAnnotations builds its attribute constructions on use, not in static fields
fix: a generated TryParse ends at its return, with no unreachable tail
…erface

test: the fabric generates a value object whose marker comes through another assembly's interface
spike: Metalama.Testing.AspectTesting beside xunit.v3 on MTP
docs: the READMEs show the generated code for OrderId, Email and a closed set
feat: CatchAsync takes a ValueTask, and TraverseAsync reaches Option and Patch
feat: a fault mapping can withhold the fault's name from the problem
chore: retire the prober for ValueRanges' oracle kit
The fault feature carries both NameWithheld (#96) and Cause; the changelog
keeps both entries and the roadmap drops both items; the two prober seeds
this branch edited are deleted, as #108 retired the prober.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat: a fault keeps the exception that caused it, for the log and never the problem
The changelog keeps every entry and the roadmap drops both items; the
prober seed this branch edited is deleted, as #108 retired the prober.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat: assertions on a Task or ValueTask, and CMTK0003 reports one left unawaited
CaffeinatedCoder and others added 25 commits October 29, 2026 16:21
`oracles seeds` read any failed test as a catch, so a SQL Server fixture that
never started (2022 under QEMU on an arm64 Mac) credited both SQL Server seeds
while every test failed in `SqlServer.InitializeAsync`, and the tool exited 0.

The tool now runs every oracle the selected seeds name on a copy with no seed
first, built and filtered as the seeded runs are. A seed is CAUGHT only when
its oracle passes there and fails with the seed in a test of its own. A run
that never reached the tests is ERROR, exit 4, with or without the seed. The
TRX decides that, as measured with xunit.v3 4.0.2:
- a fixture that fails to set up fails each test with one of xunit's own
  messages;
- a fixture that fails in Dispose is only an `error` counter;
- a crashed host writes no TRX and exits other than 0 or 2;
- a run whose every test is skipped ran nothing.
A failing baseline is ERROR too. Decision 118 records the rule, and the
README, CONTRIBUTING and AGENTS say what ERROR means at release.

Input class: the ways a run fails outside a test's own code. SeedVerdictTests
covers each kind measured above and every setup message xunit.v3.core 4.0.2
holds. With the setup check and the baseline seeded out, three of those tests
fail, the dead fixture of #151 among them.

Closes #151.

Defect-Class: TOOL
Found-By: OR
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs: the 2.3.0 changelog lists the Generators fixes, the generated code and the README introduction
…r a branch

An assertion of CodoMetis.TypeKit.Testing on a Task or ValueTask fails the
test only through the task it returns. CMTK0003 reported one dropped as a
statement, but not one that returns a plain Task followed by ConfigureAwait or
WaitAsync (ShouldBeNone, command ShouldBeSuccess, the expected-value
ShouldBeError, ShouldBeAbsent, ShouldBeNull), nor any assertion in a branch of
a ?:, ?? or switch expression so wrapped. Tasks.PassesOn now hands on a plain
Task's ConfigureAwait and WaitAsync too, and the pending-assertion check runs
inside the Producers loop that already judged each branch of a dropped result,
so both forms of the rule take one path. Awaited, held, returned and `_ =`
uses stay silent. Decision 117 covers the wider reach.

Input class: AIS, a call's task dropped by a statement. There was no catalogue
for it; Variants/DroppedTaskShapes is new: 25 calls (every Testing assertion
on a Task and a ValueTask, and a call returning a Task of a Result) by 9
pass-throughs (none, both ConfigureAwait overloads, every WaitAsync overload,
one chained) by 6 forms (the call, the lowered static form, ?., both branches
of ?:, ??'s right operand, both arms of a switch) by 6 uses (dropped, a
lambda's body, awaited, discarded, held, returned): 6,831 statements,
DroppedTaskVariantTests. Seeded three ways: without the plain Task in PassesOn
the 12 plain-Task assertions fail; with the assertion check limited to the
statement's own call all 24 assertions fail; without the await condition all 24
assertions fail on false reports of their awaited forms (58 for ShouldHold on
a Task alone). No sibling beyond #152's two was found.

Claim T02-a-wrapped-assertion-is-reported quotes the README's new sentence.

Closes #152

Defect-Class: AIS
Found-By: OR
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#151 merges first and takes 0117, and the records are numbered in merge
order, so this record becomes 0118: its file, heading, index rows and the
links from design/analyzers.md and the roadmap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…assertions

# Conflicts:
#	design/analyzers.md
fix: oracles seeds credits a seed only against a passing baseline
…3-new-api-forms

# Conflicts:
#	design/adr/README.md
…Any of results

The maintainer took #159 into 2.3.0: CMTK0003 reports a Task.WhenAny of
tasks of results dropped without await, which code built on 2.2.0 can
contain. Such a statement loses every result, so the report finds a defect.
Decision 118, its index row, design/analyzers.md and the Analyzers README's
"What a new version may change" name the exception beside the rule, so the
README's promise stays true.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…orms

docs: an Error may learn, in a minor version, a form only that version's new API writes
…inators

An assertion of CodoMetis.TypeKit.Testing fails a test only through its task.
CMTK0003 now reports one handed to Task.WhenAll, Task.Run or Task.WhenEach
whose task is dropped, and one given to Task.WhenAny or Task.WaitAny, which
finish with the first task and never show a failure, awaited or not. It
counts arguments, arrays, collection expressions, a Select, a query, a
ToList, a method group and an async lambda that awaits one (no catch between).
Tasks the caller holds, an assertion discarded where it is made and one given
to a call that returns no task stay silent; Task.WaitAll throws, so it is
silent. A WhenAny of assertions converted to a plain Task or waited for, and
a loop that never reads the assertions its header made, are reported too
(#157).

For tasks of results, the same sweep found four forms 2.2.0 missed, which
ship in 2.3.0 under decision 0119: a dropped Task.WhenAny (Tasks.PendingOutcome
now sees a task of a task, #159), Wait() on a ?:, ?? or switch (AnalyzeWait
goes through Producers, #160), an array written in Task.WaitAll or WaitAny
(each element's call, #161) and a dropped Task.WhenEach (#162). A WhenAny of
held tasks returns one of them and is now silent, where 2.2.0 reported it
awaited.

Input class: AIS, a call's task dropped or lost by a statement.
Variants/DroppedTaskShapes now has 25 calls by 9 pass-throughs by 39 forms by
11 uses, run one compilation per call and form (830 cases,
DroppedTaskVariantTests): a single compilation per call overflowed the
verifier's recursive matching. Seeded 18 ways, each caught: without the
combinators 378 cases fail, without HandsOn's checks 48, a task of a task one
level deep 3 plus the WhenAny fact, WhenAny of held tasks not stored 1 plus
the fact, Wait on the own call only 3, arrays 2, method groups 18, the await
jump 48, the catch check 24, Task.Run 48, WhenEach 25, the assertion loop 48,
the conversion's assertion path 72, WaitAny 48, query syntax 24, calls that
return no task 24, Wait's assertion path 72, WhenEach of results 1.

Claims T02-combined-assertions-are-reported, T02-wait-all-observes-assertions
and T02-when-any-of-results-is-reported quote the README.

Defect-Class: AIS
Found-By: VA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…that release

The maintainer widened decision 118's one exception: a form an Error misses,
found before its version is tagged, is reported in that version when it
loses what the rule protects in every build, though code built on the
previous minor can contain it. 2.3.0's cases are #159 to #162; the rule stops
at the tag, after which decision 34 holds. 0118's index row says it is
widened by 0119.

The Analyzers README's "What a new version may change" lists the four
2.2-writable forms and what to write instead, the changelog's Changed section
says an upgrade can fail a build with them, the CMTK0003 row of
design/analyzers.md lists every form the PR reaches, and claim
T02-a-few-2-2-forms-are-reported quotes the README.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sertions

fix: CMTK0003 reports assertions and results lost through pass-throughs, branches and Task's combinators
…to the rest

AGENTS.md loads into every agent session. Five sections repeated what another
file holds or detailed what an agent needs only at one moment:

- Finding code: the graph's measured gaps move to design/code-graph.md (new,
  linked from design/plan.md); AGENTS.md keeps the rule and the gaps in one line.
- Commands: the BuildVerdict, BashGuard and session-start paragraph shrinks to
  what the verdict prints and what the guard refuses.
- Issues: the label legend, epics and PR checks are CONTRIBUTING's; what stays
  is what trips agents here.
- Workflow step 7: the oracle-kit mechanics are test/oracles/README.md's.
- Releasing: CONTRIBUTING's "Releasing" holds the steps; the seeds, the
  arm64 image and the never-delete-a-seed rule stay.

"Rules that are easy to break" is unchanged, and so is every heading another
document cites. 22,230 to 17,956 characters, about 1.1k tokens less per
session start. Conventions tests pass (2,002) and docs-query check is clean.

Refs #167

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs: AGENTS.md keeps what an agent needs before it acts, and points to the rest
…s as planned

A net11.0 asset joins a 2.x minor only with the net10.0 group's dependency
floors (a higher floor is NU1605 for an application that holds the lower
version, measured on SDK 11 RC1), a superset surface that needs no C# 15 to
call, the same generated members on every target and LangVersion 14 until an
implementation needs 15. The SDK 11 spike (#170) comes first. 3.0.0 stays C#
15 unions and the net10.0 drop, timed to .NET 12 LTS; refines decision 44.

Closes #169

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A union new on net11.0 is an addition, not a second contract: measured on
SDK 11 RC1, a net10.0 consumer sees nothing, a LangVersion 14 net11.0
consumer who holds the value builds, and one who matches on it gets CS9327.
3.0.0 keeps the net10.0 drop and the change of an existing contract. The
record is renamed to say so; the dependency-floor rule is unchanged.

Refs #169

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…gh catches that cannot keep them

A Task.WhenAny inside a Task.WhenAll or a Task.Run completes with the faulted
assertion as its value, so awaiting the outer task saw no failure and the
rule said nothing (#165). HandsOn now returns how deep the root holds the
failure instead of a bool: 0 where its task faults with it (WhenAll, Run),
one more for each task that completes with it as a value (WhenAny, WhenEach,
an async lambda that returns it), one fewer for each await that unwraps one.
DroppedAssertion treats an awaited, waited-for or converted combinator as
observing only a failure at depth 0, so the same path reports
await Task.WhenAll(Task.WhenAny(...)), await Task.Run(() => Task.WhenAny(...)),
their Wait() and plain-Task forms, at any depth, and keeps
await await Task.WhenAny(...) silent. Task.WaitAll of a WhenAny was already
reported once, at the WhenAny's conversion to a plain Task.

A catch around an awaited assertion in an async lambda counted as observing
the failure whatever it did (#166). EnclosingAsyncFunction now stops only at
a catch that keeps it: the await is in its try block, its type is or is a
base of TypeKitAssertionException (resolved in the Testing assembly by its
metadata name), and its handler can complete (control flow: endpoint
reachable, or a return/break leaves it). A constant false filter leaves the
handler unreachable, so control flow already reads it as never completing:
an explicit filter check, seeded, was caught by nothing, and is not kept.
catch { throw; }, a log and throw;, throw new ..., catch
(OperationCanceledException) and when (false) now report. A filter that is
no constant counts as keeping the failure: an Error reports only a failure
lost whatever the filter answers. Both forms are written only with 2.3.0's
Testing API, so decision 0118 covers them; no 2.2.0 build reports more.

Input class: AIS, an assertion's task lost by a statement.
Variants/DroppedTaskShapes gains 42 forms, all assertion-only: every
combinator inside each other, three and four deep, arrays, Selects and async
lambdas of WhenAny that return it, await it once, twice and three times;
every catch kind (try/finally, rethrow, log and rethrow, throw another,
unrelated type, false filter, nested tries, await in a handler or a finally)
against silent controls (catch all, catch the assertion's exception, true
filter, opaque filter, conditional rethrow, return, a later clause, nested
tries that keep it); and Task.WaitAll/WaitAny of nested combinators. 1837
cases, from 830. IgnoredOutcomeAnalyzerTests holds the issues' snippets and a
look-alike TypeKitAssertionException of another type. Seeded 11 ways, each
caught by DroppedTaskVariantTests (failing cases): the producer's depth (192),
WhenAny's depth (168), an async lambda's return (48), await unwrapping (48),
a catch's try block only (48), the exception type (48), the rethrow check
(144), its exit points (24), the exception's metadata name (48), the base
chain (240), and an unreachable handler read as completing, which turns the
when (false) rows red (24).

README (CMTK0003) says so; claims T03-a-nested-when-any-is-reported,
T03-a-catch-that-cannot-keep-the-failure-is-reported and
T03-an-unknown-filter-counts-as-catching quote it.

Closes #165
Closes #166

Defect-Class: AIS
Found-By: OR
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Metalama is one version for every target and TypeKit follows the maintained
Metalama year in a minor (1.5.0 already raised the floor in one). The move is
not only a restore error: a woven net11.0 project goes from C# 14 to 15, an
unsupported IDE host fails silently, and Metalama 2027 drops Visual Studio
2022. The record lists the conditions (stable 2027.0, a license-free CI and
clean-container build, the ceiling and the 2.4.x line documented), corrects
the rule on the language version and the SDK, and adds the measurements.

Refs #169, #170, #173

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The Semantic Versioning FAQ calls a dependency update with an unchanged public
API compatible, so the first rule no longer refuses a higher floor in a minor,
on every target or on net11.0 alone. The raise is documented instead: the
changelog names the dependency, the floors, who it reaches (NU1605 for a
project that holds the lower version) and both ways out, and the smoke test
shows it. Metalama is the same rule on the dependency with the widest reach.
The record is renamed to say so; the roadmap and the index row follow.

Refs #169

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
docs: decision 120, a net11.0 target and documented floor raises join a 2.x minor
…-rethrow

fix: CMTK0003 reports assertions lost in nested combinators and through catches that cannot keep them
The 2.3.0 section of CHANGELOG.md is dated 2026-10-29, and its links name blob/v2.3.0/,
so the release notes open the pages as they were released.

AnalyzerReleases.Shipped.md gets its "Release 2.3.0" header. 2.3.0 adds no rule: CMTK0003
and CMTK0009 widen rules shipped earlier, and Unshipped.md is empty. The empty
"Release 2.2.0" header goes: Roslyn's release tracking refuses any header after an empty
release (RS2007, measured with 2.3.0 empty and with 2.3.0 carrying a changed rule), so
2.2.0's header would have failed this release and every later one. CONTRIBUTING's step 3
says so.

Closes #174

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs: close 2.3.0: the changelog dated, the analyzer release recorded
@CaffeinatedCoder CaffeinatedCoder added this to the 2.3.0 milestone Oct 29, 2026
@CaffeinatedCoder CaffeinatedCoder added chore Build, CI, tooling, packaging, refactoring with no behaviour change area/release Versioning, changelogs, packaging and release wiring labels Oct 29, 2026
@CaffeinatedCoder
CaffeinatedCoder merged commit edeb90c into main Oct 29, 2026
22 of 23 checks passed
@CaffeinatedCoder
CaffeinatedCoder deleted the release/2.3.0 branch October 29, 2026 21:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/release Versioning, changelogs, packaging and release wiring chore Build, CI, tooling, packaging, refactoring with no behaviour change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Epic: 2.3.0

1 participant