diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index bc9bc27..1978504 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -17,7 +17,7 @@
"rollForward": false
},
"purview.build": {
- "version": "0.9.0",
+ "version": "0.9.1",
"commands": [
"purview-build"
],
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index 76e1703..c287e7f 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -13,6 +13,9 @@ jobs:
name: Build and test
uses: purview-dev/build/.github/workflows/purview-build.yml@main
with:
+ # Pinned to the version in .config/dotnet-tools.json, so CI runs exactly what `just` runs locally
+ # instead of whichever Purview.Build was latest when the job started (which warns on every run).
+ build-version: "0.9.0"
# The shared workflow defaults to the 10.0.x SDK, but this repository targets net11.0 and pins
# the .NET 11 SDK in global.json. Without the matching SDK the runner cannot resolve global.json
# ("A compatible .NET SDK was not found") and every step fails, starting with `dotnet tool
@@ -20,10 +23,45 @@ jobs:
# NETSDK1045 on the net11.0 target, because the 10.0.x SDK cannot build it.
# Keep in sync with `sdk.version` in global.json.
dotnet-version: "11.0.100"
+ # Explicit, so PrWorkflow_GivenTheTestGates_RunsEveryTestInBothConfigurations can prove the
+ # Release gate still runs tests; the workflow default is true, but a default is invisible here.
+ run-tests: true
run-pack: true
validate-pack: true
secrets: inherit
+ # The shared pipeline runs a single configuration (Release, the tool's default). A failure that only
+ # reproduces in Debug would therefore reach a developer's `just test` (which defaults to Debug) and
+ # never CI. The cross-platform job below also runs Debug, but filters to the parity producers; this
+ # job builds the whole solution and runs every test in Debug.
+ debug:
+ name: Build and test (Debug)
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+
+ # Same reason as the cross-platform job: the SDK comes from global.json, not a floating
+ # version plus a feed quality.
+ - uses: actions/setup-dotnet@v6
+ with:
+ global-json-file: global.json
+
+ # Repository.UnitTests drives the vitest suites from the .NET test run, so the JavaScript
+ # dependencies have to be installed first.
+ - uses: oven-sh/setup-bun@v2
+ with:
+ bun-version-file: package.json
+
+ - run: bun install --frozen-lockfile
+
+ - name: Build the solution (Debug)
+ run: dotnet build src/ZodSharp.slnx --configuration Debug
+
+ - name: Run every test (Debug)
+ run: >-
+ dotnet test --solution src/ZodSharp.slnx --configuration Debug
+ --no-restore --no-build --treenode-filter '/*/*/*/*'
+
# The shared pipeline runs the C# suite only, so the TypeScript/Zod side of the cross-platform
# parity guarantee (docs/wiki/quality/Guarantees-and-Limitations.md) would otherwise never run in CI.
# The vitest suite validates the JSON the C# cross-platform tests write, so the C# tests must run
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 01283a5..781a38a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -28,6 +28,9 @@ jobs:
# file name the policy is registered against.
- uses: purview-dev/build/actions/purview-release@main
with:
+ # Pinned to the version in .config/dotnet-tools.json, so the release runs exactly what the
+ # pull request validated instead of whichever Purview.Build was latest when the job started.
+ build-version: "0.9.0"
release-mode: NuGet
trusted-publishing: true
# The nuget.org profile name, not the sign-in email.
diff --git a/docs/wiki/development/Release-Flow.md b/docs/wiki/development/Release-Flow.md
index 35e021d..00ec031 100644
--- a/docs/wiki/development/Release-Flow.md
+++ b/docs/wiki/development/Release-Flow.md
@@ -27,7 +27,7 @@ rather than passing.
| Gate | What it proves | Local | CI |
|---|---|---|---|
-| Build, lint and tests | The solution builds warning-free, CSharpier reports no change, and every test project passes (including the cross-platform vitest suites, through `Repository.UnitTests`). | `just build`, `just lint-check`, `just test` | `pr.yml` `build` |
+| Build, lint and tests | The solution builds warning-free, CSharpier reports no change, and every test project passes (including the cross-platform vitest suites, through `Repository.UnitTests`). | `just build`, `just lint-check`, `just test` | `pr.yml` `build` (Release, with lint and pack) and `pr.yml` `debug` (the whole solution and every test in Debug) |
| Pack and pack validation | Every package packs; package validation holds the public API to the 2.4.2 baseline; `.config/purview-build.json` `PackValidation` finds exactly the declared content (`RequiredContent`, `RequireExplicitContent`) and none of the `ForbiddenContent`. | `just pipeline-pack-validate` | `pr.yml` `build` (`run-pack`, `validate-pack`) |
| Forbidden-content rule proof | Every `ForbiddenContent` rule rejects a package: a matching file is injected into scratch copies of each package and the real `purview-build` validation must fail, per package and per rule. | `just verify-pack-rules` | `pr.yml` `packed-consumers` |
| Packed generator | The IL-merged generator in the `.nupkg` loads and generates in a net10.0 consumer (it has regressed twice while the in-repo tests, which use the unmerged generator, stayed green). | `just smoke-packed-generator` | `pr.yml` `packed-generator` |
@@ -74,7 +74,7 @@ just publish-aot-sample # Native AOT minimal API sample an
| Workflow | Trigger | Pipeline mode |
|---|---|---|
-| `pr.yml` | pull requests against `main` | `purview-build.yml` with `run-pack: true`, `validate-pack: true` |
+| `pr.yml` | pull requests against `main` | `purview-build.yml` with `run-pack: true`, `validate-pack: true` (Release), plus a Debug build and full test run |
| `release.yml` | pushes to `main` | `purview-release.yml` with `release-mode: NuGet` |
Both consume `.config/purview-build.json`:
diff --git a/src/tests/Blazor.UnitTests/GeneratedSchemaTests.cs b/src/tests/Blazor.UnitTests/GeneratedSchemaTests.cs
index 59e5136..7da8898 100644
--- a/src/tests/Blazor.UnitTests/GeneratedSchemaTests.cs
+++ b/src/tests/Blazor.UnitTests/GeneratedSchemaTests.cs
@@ -236,9 +236,14 @@ public async Task DataAnnotationsValidator_GivenAsyncOnlyMemberFieldChange_IsPen
var form = context.Render(editContext, FormHost.DataAnnotations());
FieldIdentifier field = new(cat, nameof(GeneratedCat.Name));
- // Act
- await form.ChangeAsync(editContext, field);
- var pending = editContext.IsValidationPending(field);
+ // Act - read the pending state inside the renderer's dispatch operation. The validation suspends at its
+ // first await, and its continuation is queued on that same dispatcher, so sampling after the call returns
+ // races the completion (a Release run on a busy Linux runner resolves before the test thread resumes).
+ var pending = await form.InvokeAsync(() =>
+ {
+ editContext.NotifyFieldChanged(field);
+ return editContext.IsValidationPending(field);
+ });
await FormHost.WaitUntilAsync(() => !editContext.IsValidationPending(field), "resolved");
// Assert
diff --git a/src/tests/Repository.UnitTests/ReleaseGateTests.cs b/src/tests/Repository.UnitTests/ReleaseGateTests.cs
index cbabdf7..8e49680 100644
--- a/src/tests/Repository.UnitTests/ReleaseGateTests.cs
+++ b/src/tests/Repository.UnitTests/ReleaseGateTests.cs
@@ -265,6 +265,34 @@ CancellationToken cancellationToken
await Assert.That(job).Contains("dotnet workload install wasm-tools");
}
+ ///
+ /// The shared pipeline runs a single configuration, so without the Debug job CI would only ever exercise
+ /// Release while `just test` runs Debug locally. A test that fails only in Debug would reach a developer and
+ /// never the pull request. Both configurations must build the solution and run every test.
+ ///
+ [Test]
+ public async Task PrWorkflow_GivenTheTestGates_RunsEveryTestInBothConfigurations(
+ CancellationToken cancellationToken
+ )
+ {
+ // Arrange
+ var text = await File.ReadAllTextAsync(
+ Path.Combine(RepositoryRoot.Value, ".github", "workflows", "pr.yml"),
+ cancellationToken
+ );
+
+ // Act
+ var jobs = Jobs(text).ToArray();
+ var release = jobs.SingleOrDefault(static j => j.Name == "build").Body ?? string.Empty;
+ var debug = jobs.SingleOrDefault(static j => j.Name == "debug").Body ?? string.Empty;
+
+ // Assert
+ await Assert.That(release).Contains("run-tests: true");
+ await Assert.That(debug).Contains("dotnet build src/ZodSharp.slnx --configuration Debug");
+ await Assert.That(debug).Contains("dotnet test --solution src/ZodSharp.slnx --configuration Debug");
+ await Assert.That(debug).Contains("--treenode-filter '/*/*/*/*'");
+ }
+
///
/// Compiler-host assemblies must stay forbidden everywhere NuGet hands files to an application, and the generator
/// and code fixes everywhere an application loads assemblies from. scripts/test-pack-validation-rules.ps1