diff --git a/.github/workflows/verify-generated.yml b/.github/workflows/verify-generated.yml
new file mode 100644
index 0000000..5b4f142
--- /dev/null
+++ b/.github/workflows/verify-generated.yml
@@ -0,0 +1,48 @@
+name: Verify Generated Files
+
+# Source-generator output under Semantics.Quantities/Generated/ is committed to the
+# repo (EmitCompilerGeneratedFiles=true). This job rebuilds and fails if the committed
+# files are stale, so a generated file can never drift from its generator.
+#
+# NOTE: this is a standalone workflow on purpose — dotnet.yml is centrally synced from a
+# shared template across ktsu repos, so a step added there would be overwritten. If this
+# check belongs in the shared KtsuBuild pipeline long-term, move it there and delete this.
+
+on:
+ pull_request:
+ paths-ignore: ["**.md"]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: verify-generated-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ verify-generated:
+ name: Generated files up to date
+ # windows-latest so git's autocrlf matches the committed CRLF line endings and the
+ # diff doesn't flag end-of-line differences.
+ runs-on: windows-latest
+ timeout-minutes: 15
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup .NET SDK
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: "10.0.x"
+
+ - name: Build (regenerates source-generator output into Generated/)
+ run: dotnet build Semantics.Quantities/Semantics.Quantities.csproj -c Release
+
+ - name: Fail if generated files are stale
+ shell: bash
+ run: |
+ if ! git diff --exit-code -- 'Semantics.Quantities/Generated/'; then
+ echo "::error::Generated files are out of date. Run 'dotnet build Semantics.Quantities/Semantics.Quantities.csproj' and commit the changes under Semantics.Quantities/Generated/."
+ exit 1
+ fi
diff --git a/Semantics.Paths/Semantics.Paths.csproj b/Semantics.Paths/Semantics.Paths.csproj
index 426ab04..e498fea 100644
--- a/Semantics.Paths/Semantics.Paths.csproj
+++ b/Semantics.Paths/Semantics.Paths.csproj
@@ -2,7 +2,7 @@
- net10.0;net9.0;net8.0;net7.0;net6.0;net5.0;netstandard2.0;netstandard2.1
+ net10.0;net9.0;net8.0;netstandard2.0;netstandard2.1
$(NoWarn);CA1716;CA2225;IDE0032;CA1866;CA2249;IDE0056;IDE0057
diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.UnitsGenerator/Units.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.UnitsGenerator/Units.g.cs
index 1b1e59a..ea08567 100644
--- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.UnitsGenerator/Units.g.cs
+++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.UnitsGenerator/Units.g.cs
@@ -5422,9 +5422,6 @@ public static class Units{
/// Singleton FaradPerMeter instance.
public static readonly FaradPerMeter FaradPerMeter = new FaradPerMeter();
- /// Singleton FootPerSecond instance.
- public static readonly FootPerSecond FootPerSecond = new FootPerSecond();
-
/// Singleton Foot instance.
public static readonly Foot Foot = new Foot();
@@ -5434,6 +5431,9 @@ public static class Units{
/// Singleton FootLambert instance.
public static readonly FootLambert FootLambert = new FootLambert();
+ /// Singleton FootPerSecond instance.
+ public static readonly FootPerSecond FootPerSecond = new FootPerSecond();
+
/// Singleton Gallon instance.
public static readonly Gallon Gallon = new Gallon();
diff --git a/Semantics.Quantities/Semantics.Quantities.csproj b/Semantics.Quantities/Semantics.Quantities.csproj
index 12e8d9d..0deb3f1 100644
--- a/Semantics.Quantities/Semantics.Quantities.csproj
+++ b/Semantics.Quantities/Semantics.Quantities.csproj
@@ -2,7 +2,7 @@
- net10.0;net9.0;net8.0;net7.0
+ net10.0;net9.0;net8.0
$(NoWarn);CA1716;CA2225;KTSU0003;IDE0032
true
Generated
diff --git a/Semantics.Strings/Semantics.Strings.csproj b/Semantics.Strings/Semantics.Strings.csproj
index 04d2585..4b57fd6 100644
--- a/Semantics.Strings/Semantics.Strings.csproj
+++ b/Semantics.Strings/Semantics.Strings.csproj
@@ -2,7 +2,7 @@
- net10.0;net9.0;net8.0;net7.0;net6.0;net5.0;netstandard2.0;netstandard2.1
+ net10.0;net9.0;net8.0;netstandard2.0;netstandard2.1
$(NoWarn);CA1716;CA1866;CA2249;IDE0057
diff --git a/Semantics.Test/Quantities/VectorQuantityTests.cs b/Semantics.Test/Quantities/VectorQuantityTests.cs
index 13499a5..a8d3087 100644
--- a/Semantics.Test/Quantities/VectorQuantityTests.cs
+++ b/Semantics.Test/Quantities/VectorQuantityTests.cs
@@ -165,13 +165,11 @@ public void Speed_Plus_Speed_Returns_Speed()
}
// ------------------------------------------------------------- V0 - V0
- // Locked design decision in #52: V0 - V0 should return the same V0 of T.Abs(a - b).
- // Generator currently emits unsigned subtraction via the SemanticQuantity base, which
- // can produce a negative magnitude. Tracked as a follow-up.
+ // #52: V0 - V0 returns the same V0 of T.Abs(a - b). The generated derived operator
+ // wins overload resolution over PhysicalQuantity's plain (signable) subtraction.
[TestMethod]
- [Ignore("Locked in #52: V0 - V0 should return the same V0 of T.Abs(a - b). Generator currently emits unsigned subtraction.")]
- public void Mass_Minus_Mass_Returns_Absolute_Difference_Pending52()
+ public void Mass_Minus_Mass_Returns_Absolute_Difference()
{
Mass a = Mass.FromKilogram(3.0);
Mass b = Mass.FromKilogram(5.0);
@@ -180,20 +178,18 @@ public void Mass_Minus_Mass_Returns_Absolute_Difference_Pending52()
}
// ---------------------------------------------------- V0 non-negativity
- // Tracked in #50: factories on Vector0 quantities should reject negative inputs
- // with ArgumentException. The current generator does not emit guards.
+ // #50: factories on Vector0 quantities reject negative inputs with ArgumentException
+ // via Vector0Guards.EnsureNonNegative (the guard runs after unit conversion).
[TestMethod]
- [Ignore("Tracked in #50: V0 factories should reject negative inputs.")]
- public void Speed_From_Negative_Throws_Pending50()
+ public void Speed_From_Negative_Throws()
{
_ = Assert.ThrowsExactly(
() => Speed.FromMeterPerSecond(-1.0));
}
[TestMethod]
- [Ignore("Tracked in #50: V0 factories should reject negative inputs.")]
- public void Mass_From_Negative_Throws_Pending50()
+ public void Mass_From_Negative_Throws()
{
_ = Assert.ThrowsExactly(
() => Mass.FromKilogram(-1.0));
diff --git a/docs/migration-guide-2.0.md b/docs/migration-guide-2.0.md
index 0ebdd8d..7e2dfad 100644
--- a/docs/migration-guide-2.0.md
+++ b/docs/migration-guide-2.0.md
@@ -214,6 +214,10 @@ different dimensions throws `ArgumentException`; equality across dimensions is
| audio-engineering `Percent` | the `Percent` **unit** on the Dimensionless dimension: `Ratio.FromPercent(50)` and `ratio.In(Units.Percent)` |
| audio-engineering `Gain` record struct | `Gain` is now a generated semantic overload of `Ratio` (a record class, non-negative, widens implicitly to `Ratio`); `Unity`, `Silence`, the `Decibels` conversions, and `*` live in a partial |
+## Target frameworks
+
+The out-of-support runtimes `net5.0`, `net6.0`, and `net7.0` were dropped — `System.Text.Json` 10 (and friends) no longer ship assets for them. `ktsu.Semantics.Quantities` now targets `net8.0`–`net10.0` (it requires `INumber`, so it has no `netstandard` target). `Semantics.Strings` and `Semantics.Paths` target `net8.0`–`net10.0` plus `netstandard2.0`/`netstandard2.1`, so consumers on older runtimes still resolve via `netstandard`.
+
## What didn't change
- `Semantics.Strings` and `Semantics.Paths` — same namespaces, same types;
@@ -224,7 +228,6 @@ different dimensions throws `ArgumentException`; equality across dimensions is
through the generated `Ratio`, and (for the logarithmic ones) are
generated from `logarithmic.json`. `Percent` became a unit and `Gain` a
generated `Ratio` overload — see the removed-APIs table.
-- Target frameworks: `net7.0` through `net10.0`.
- Quantities remain generic over the numeric storage type
(`where T : struct, INumber`).
- `UnitSystem` and `UnitConversionException` keep their names and meaning.