From 95ec0a0fa570203f1dd73ac4e086c3d151a45737 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sat, 5 Sep 2026 19:44:03 +0000 Subject: [PATCH] The dependency gate is about packaging, not about the build leg I merged #1181 green on net10.0 and it turned master's C# Test red on "netstandard" -- a reference assembly the netstandard2.0 leg resolves against and net10.0 does not. The gate asserted an exact set measured on one leg, so it was asserting a fact about the build rather than about the dependencies. Which framework assemblies a leg resolves is not a packaging decision. Which third-party ones it references is. So the third-party set is asserted exactly and in both directions, and the framework ones only as a set nothing may exceed: a leg resolving fewer is not an event, a leg pulling in something new -- System.Text.Json arriving with Core/Serialization is the case in point -- still fails. "netstandard" was also being counted as third-party by the second test, since it does not start with "System.", which is why both failed rather than one. There is now one predicate saying what a framework assembly is, and both tests use it. The test's own remarks record the mistake, because the shape of it is the useful part: a check that passes on the leg it was written against and fails on another is not a weaker check, it is a check of something else. Full suite green locally; the leg that caught this is CI's. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Docs/Contributing/Packaging.md | 5 +- .../UnitTests/Core/KernelDependenciesTest.cs | 72 +++++++++++-------- 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/Sources/AngouriMath/Docs/Contributing/Packaging.md b/Sources/AngouriMath/Docs/Contributing/Packaging.md index fe99ba11f..6effd0769 100644 --- a/Sources/AngouriMath/Docs/Contributing/Packaging.md +++ b/Sources/AngouriMath/Docs/Contributing/Packaging.md @@ -144,7 +144,10 @@ typeof(AngouriMath.Entity).Assembly # measured by reflection on the built ne ANTLR-generated lexer and parser default their error streams to it.) This said 13 until `System.Text.Json` arrived with `Core/Serialization` — a framework assembly, so no consumer's restore changed, and nothing noticed for the same reason. `KernelDependenciesTest` is the gate §7 - asked for and now holds both counts. + asked for. It asserts the **third-party** set exactly, in both directions, and the framework ones + only as a set nothing may exceed — which framework assemblies a build resolves depends on the + target framework leg, and `netstandard` appears on the netstandard2.0 one and not on `net10.0`. + A leg resolving fewer of them is not a packaging event; one pulling in something new is. **Twelve capabilities × 68 node types is the reason the kernel is one assembly.** A capability written as an abstract member of `Entity` cannot be in a different package than `Entity`, whatever anyone diff --git a/Sources/Tests/UnitTests/Core/KernelDependenciesTest.cs b/Sources/Tests/UnitTests/Core/KernelDependenciesTest.cs index 77e798044..14d1a18fd 100644 --- a/Sources/Tests/UnitTests/Core/KernelDependenciesTest.cs +++ b/Sources/Tests/UnitTests/Core/KernelDependenciesTest.cs @@ -56,6 +56,22 @@ public sealed class KernelDependenciesTest "Antlr4.Runtime.Standard", }; + /// + /// Whether a reference is part of the framework rather than something a consumer restores. + /// + /// + /// netstandard is on this list because it is the reference assembly the + /// netstandard2.0 leg resolves against, and it appears or does not depending on which leg + /// is loaded. The first version of this file asserted an exact set that had been measured + /// on one leg, passed locally on net10.0, and failed the C# Test workflow on + /// exactly that name. Which framework assemblies appear is a fact about the build leg; + /// which third-party ones appear is the packaging decision, and only the second is + /// asserted exactly. + /// + private static bool IsFramework(string name) => + name.StartsWith("System.", System.StringComparison.Ordinal) + || name is "System" or "netstandard" or "mscorlib"; + private static string[] Referenced() => typeof(MathS).Assembly .GetReferencedAssemblies() @@ -63,53 +79,49 @@ private static string[] Referenced() => .OrderBy(name => name, System.StringComparer.Ordinal) .ToArray(); + /// + /// The framework assemblies seen so far, across the legs this has run on. A superset rather + /// than an equality: a leg that resolves fewer of them is not a packaging event, while one + /// that pulls in something new — System.Text.Json arriving with + /// Core/Serialization is the case in point — is exactly what wants seeing. + /// + private static readonly string[] Framework = + { + "System.Collections", + "System.Collections.Concurrent", + "System.Console", + "System.Linq", + "System.Linq.Expressions", + "System.Memory", + "System.Runtime", + "System.Runtime.Numerics", + "System.Text.Json", + "System.Threading", + "netstandard", + }; + [Fact] public void TheKernelReferencesNothingItIsNotRecordedAsReferencing() { - var expected = new[] - { - "Antlr4.Runtime.Standard", - "GenericTensor", - "HonkSharp", - "Numbers", - "System.Collections", - "System.Collections.Concurrent", - "System.Console", - "System.Linq", - "System.Linq.Expressions", - "System.Memory", - "System.Runtime", - "System.Runtime.Numerics", - "System.Text.Json", - "System.Threading", - }; - - var actual = Referenced(); + var recorded = Framework.Concat(ThirdParty).ToList(); + var added = Referenced().Except(recorded).ToList(); - var added = actual.Except(expected).ToList(); Assert.True(added.Count == 0, $"the kernel references {added.Count} assemblies this list does not record: " + string.Join(", ", added) + ". Adding one is a packaging decision — see Docs/Contributing/Packaging.md §11 — " + "so record it here in the same change that adds it."); - - // The other direction, so the list cannot outlive what it describes: a dependency that - // goes away should be deleted here rather than left asserting nothing. - var gone = expected.Except(actual).ToList(); - Assert.True(gone.Count == 0, - $"{gone.Count} assemblies are recorded here and no longer referenced, and should be " - + "deleted: " + string.Join(", ", gone)); } /// - /// The number that matters to a consumer, separately from the framework ones, because it - /// is what a restore actually fetches. + /// The set that matters to a consumer, separately from the framework ones, because it is + /// what a restore actually fetches. Asserted exactly, in both directions. /// [Fact] public void TheThirdPartyDependenciesAreTheFourThatWereAgreed() { var actual = Referenced() - .Where(name => !name.StartsWith("System.", System.StringComparison.Ordinal)) + .Where(name => !IsFramework(name)) .OrderBy(name => name, System.StringComparer.Ordinal) .ToArray();