Skip to content

Feat/zodsharp 3 blazor - #9

Merged
kieronlanning merged 22 commits into
mainfrom
feat/zodsharp-3-blazor
Nov 3, 2026
Merged

kieronlanning merged 22 commits into
mainfrom
feat/zodsharp-3-blazor

Conversation

@kieronlanning

Copy link
Copy Markdown
Contributor

No description provided.

…rerelease.1 and SGF 1.2.2

Purview.ZodSharp 3 ships net10.0 and net11.0, which is already this repository's 'Supported'
set, so no target framework changes. Purview.ValueObjects 1.3.0-prerelease.1 is the version built
against ZodSharp 3, with trim- and AOT-safe generated JSON converters. SourceGeneratorFramework
1.2.2 stops its framework assembly leaking into the output of projects that reference the
generator as an analyzer; there was no workaround for it here to remove.

ZodSharp 3's generated schemas gain ValidateAsync, so CA1849 flags the synchronous Validate in
async tests. The failure-mapper tests, which only need a ValidationResult, now await
ValidateAsync. The integration tests exist to fold the synchronous schema through the synchronous
ToResult bridge and compare it with the asynchronous path, so they keep Validate and suppress
CA1849 for that class with the reason.
…blemDetailsOptions setting

ZodValidationProblems.ToProblem converted the errors through
ZodException.ToHttpValidationProblemDetails(registry, status), which in Purview.ZodSharp 3 builds a
writer from the registry alone: message formatting off and every new option - path naming,
issues, parameter merging, the error type resolver, the default status and the status override -
ignored. ZodSharp's exception handler writes through ZodProblemDetailsWriter with the configured
options, so a result-carried failure and the same thrown ZodException no longer produced the same
response, which this package guarantees.

ToProblem now writes through ZodProblemDetailsWriter with the host's options, overriding only the
default status a ZodResultsHttpOptions rule supplies, on a copy so the shared options are never
mutated. StatusCodeSelector is no longer applied here first: the writer applies ZodSharp 3's
precedence (default, then the highest ErrorType.HttpStatus, then the selector, then the override),
exactly as for a thrown exception. A test pins that the copy carries every settable option, so a
future option cannot be dropped silently.

Behaviour changes, matching a thrown ZodException: an ErrorType's MessageFormat now formats the
message, and a StatusCodeSelector now wins over an ErrorType status.
BuildSdk 1.2.0 rewrote the managed .editorconfig with its new version marker; the rule set is
unchanged. Its new Blazor WebAssembly, Razor class library, BFF and Blazor host detection,
PurviewBrowserSafe with the PRBLZ0001 reference check, PDS0007 (the CA1034 suppression on extension
blocks) to PDS0009 and the unmarked .editorconfig warning apply to no project in this solution -
the generator's extension blocks are emitted into consumers, and nothing here suppressed CA1034 -
and the build produced no new warning. Skills.Testing 1.2.0 passes every skill unchanged.
Every project built its net11.0 target with Features=runtime-async=on. A library compiled with the
experiment carries runtime-async methods that only a runtime implementing it can execute, and the
Mono runtime behind Blazor WebAssembly does not: ToResultAsync, ToUnitResultAsync and every async
combinator of the net11.0 packages threw PlatformNotSupportedException ("Runtime Async is not
supported on this platform") in the browser. The new packed-consumer gate found it.

The setting moves to Directory.Build.targets, where IsPackable is known, and applies to net11.0
projects that do not ship. Tests and examples keep the experiment; the libraries they reference
are compiled with the stable lowering, so the tests exercise exactly what ships.
…patibility suppressions

Purview.ZodSharp 2's generator emitted ZodSharp.ZodSchemaAttribute and
ZodSharp.Core.ErrorTypeAttribute, marked [Embedded], into every assembly it generated for, so
Purview.Results.ZodSharp and Purview.Results.ZodSharp.AspNetCore 1.0.x carry hidden copies.
Purview.ZodSharp 3 defines both once, in Purview.ZodSharp, and package validation against the
1.0.0.1 baseline reports CP0001 for their removal on both target frameworks.

No compiled consumer can bind to an [Embedded] type, so the removal breaks nothing; the generated
CompatibilitySuppressions.xml files record it, and the changelog's upgrade note is the matching
evidence.
`dotnet pack /p:GenerateCompatibilitySuppressionFile=true` writes two-space indentation; the
repository's CSharpier lint indents XML with tabs.
…sembly and Blazor Server

Every in-repo build references the libraries and the generator as projects, which exercises
nothing of what ships. Modelled on purview-dev/zodsharp's gate, `just smoke-packed-consumers`
packs every Purview.Results package to an isolated feed and NUGET_PACKAGES, guards
Directory.Build.* and Directory.Packages.props in a temp root, and builds consumers outside the
repository from the packages alone, with Purview.ZodSharp 3 and Purview.ValueObjects:

- a Native AOT console over a [GenerateResult] union, the generated AsFailure helpers and
  union-receiver factory, the ZodSharp bridge (sync and async) and a value object's type-level
  rule, published with -warnaserror and run
- a Blazor WebAssembly app whose <ZodValidator> form input then flows through the result
  pipeline, published trimmed and with RunAOTCompilation=true, served and run in a headless
  browser driven over the DevTools protocol
- a Blazor Server app over every package: a minimal API whose result maps onto a validation
  problem, a code rule's 422, a case mapping's 409 and a 200; a statically rendered form; and an
  interactive page running the same checks over a live circuit
- a net10.0 console, so the packages' other target framework keeps restoring and running

Every bin and publish folder is scanned for compiler-host assemblies, including the Results
generator and code fix, by scripts/assert-no-compiler-host-assemblies.ps1. Dependency gaps are
reported as known issues (-StrictKnownIssues fails on them).

PackValidation's ForbiddenContent grows from one rule to 25, mirroring ZodSharp's, and
`just verify-pack-rules` (scripts/test-pack-validation-rules.ps1) proves each rejects a package.
The consumers' union-declaring files join .csharpierignore.
… requests

Purview.ZodSharp 3.0.0-prerelease.1 and its Blazor package are built against the rc.3 ASP.NET
Core packages, and the WebAssembly consumers need the matching rc.3 WebAssembly SDK, so
global.json and both workflows move from rc.2 to rc.3 together.

The new packed-consumers job runs scripts/test-packed-consumers.ps1 on ubuntu-latest with
-WasmAot Require and -Browser Require (the image ships Chrome) and the .NET 10 runtime for the
net10.0 consumer, then proves every pack-validation ForbiddenContent rule. Purview.ZodSharp
3.0.0-prerelease.1, Purview.ValueObjects 1.3.0-prerelease.1 and Purview.SourceGeneratorFramework
1.2.2 are not on nuget.org yet, so until they are - or the RESULTS_DEPENDENCY_FEED repository
variable names a feed that has them - the job fails at its first step, the dependency preflight,
with what to do, instead of skipping.
…problem precedence

A new wiki page, Blazor-and-Native-AOT, covers which packages are trim- and AOT-safe (the result
type and the ZodSharp bridge) and which are server-only (the ASP.NET Core mapping), the target
frameworks, a Blazor form whose input flows through a result, and the WebAssembly AOT gap for a
value object's type-level rule.

The ZodSharp Problem Details page, the package README and the problems skill describe the new
behaviour, in which every ZodProblemDetailsOptions setting applies, with Purview.ZodSharp 3's status
precedence. The page also explains that .NET 11 request validation (AddZodSharpValidation, also
turned on by AddZodSharpBlazor) answers a [ZodSchema] parameter before a result-returning
handler runs, and that such an endpoint calls DisableValidation().

The README's ProblemDetails sample shows the ZodSharp 3 shape; README, Getting Started and the
validation skill name the ZodSharp 3 requirement and ValidateAsync with ToResultAsync; AGENTS.md
names the packed-consumer gate.
A minor prerelease. No package loses a target framework (all four still ship net10.0 and
net11.0) and no public member changed; package validation's only findings are the hidden
[Embedded] ZodSharp attribute copies, which no compiled consumer can bind to. The ZodSharp
integration packages now require Purview.ZodSharp 3, and result-carried validation problems
follow its options and status precedence; the changelog's upgrade notes cover both.
…tion

Purview.ZodSharp 3.0.0-prerelease.1's AddZodSharpBlazor() no longer turns on minimal-API parameter
validation, so the Blazor Server consumer's /api/tenants endpoint drops DisableValidation(). The
gate's existing check now proves the intended behaviour with nothing opted out: the refinement's
code rule answers 422 with tenant_id_matches_name, because the handler runs and returns its result.

The /api/tenants/prevalidated endpoint existed only to show the old behaviour and is identical to
/api/tenants now, so it is gone with its known-issue probe. The known-issue reporting and
-StrictKnownIssues are removed, so every check fails the gate.
The Blazor and Native AOT and ZodSharp Problem Details pages said AddZodSharpBlazor() turns on .NET
11 request validation, so endpoints had to call DisableValidation(), and that a type-level
[ZodRule] crashed the Mono AOT compiler. Both are fixed in Purview.ZodSharp. The pages now say
AddZodSharpBlazor() validates form models only, explain that only AddZodSharpValidation() validates
endpoint parameters (and how to register it for type validation only), and state that a value
object with a type-level rule composes with results in a WebAssembly AOT client.
The skills this repository depends on are installed by the skills CLI into the universal
.agents/skills/ location and committed with skills-lock.json, so a clone has them without
running anything. Nothing was written outside .agents/ and skills-lock.json:

    npx -y skills@1 add purview-dev/engineering-playbook -s '*' -a universal -y
    npx -y skills@1 add purview-dev/build-sdk -s '*' -a universal -y
    npx -y skills@1 add purview-dev/sourcegenerator-framework -s '*' -a universal -y
    npx -y skills@1 add purview-dev/zodsharp -s '*' -a universal -y
    npx -y skills@1 add purview-dev/value-objects -s '*' -a universal -y

.agents/ was effectively gitignored by a .agents/.gitignore that an older Purview.BuildSdk wrote
for its package mirror, with that mirror's stale, untracked copies beside it. Both were removed
from the working tree before installing; none of it was tracked.

/.claude/ is gitignored, and src/Directory.Build.props sets PurviewSkillsAgents to universal, so
the SDK's InstallPurviewSkills target no longer defaults to writing a copy for every agent.
…ention

InstalledSkillTests mirrors ZodSharp's five checks: every skills-lock.json entry is installed
under .agents/skills/, every installed folder is recorded in the lockfile, no skill authored in
skills/ is copied into .agents/skills/, no .claude/skills/ copy exists, and there is no CLAUDE.md
at the root or in .github/.
…ntion

AGENTS.md gains an "Installed skills (.agents/skills/)" section: the installed set and its
sources, the -a universal install and restore commands, why nothing goes into .claude/ and no
CLAUDE.md is added, and the test that guards it. The authored-skills section no longer says
.agents/skills/ is gitignored, and the layout table lists both skill folders. The Copilot
instructions point at the installed skills rather than at package-delivered content.
…roring

The root README, the package READMEs, Contributing and Agent Skills still said each package ships
its skills under .agents/ and that Purview.BuildSdk mirrors them into a consumer's .agents/ on
restore. That route was removed in Purview.BuildSdk 1.0.6. They now name the skill and the one
delivery route, npx skills add purview-dev/results, and Agent Skills describes the upstream skills
committed under .agents/skills/.
The pack-validation step fails twice over on a fresh runner:

- dotnet tool restore exits 1 with "The command "purview-build"
  specified in the tool manifest file is not contained in the package
  with Package Id purview.build. The commands contained in the package
  are dotnet-inspect." although Purview.Build declares purview-build.
  The .NET 11 rc.3 CLI attributes the command of the RID-specific
  dotnet-inspect tool to the manifest entry beside it; the failed attempt
  has installed every tool by then, so the next attempt succeeds.
- Every run validates by expecting purview-build to fail, so
  $LASTEXITCODE is 1 at the end of the script and GitHub's pwsh shell
  appends `exit $LASTEXITCODE`, failing the step after it printed
  "Every ForbiddenContent rule (25) rejected its file".

scripts/restore-dotnet-tools.ps1 retries a bounded number of times,
quiet on success, printing every attempt when all fail. It is now the
only place local tools are restored: `just` (ensure-pipeline-tool), the
CI pack-validation step, and the scratch repository
test-pack-validation-rules.ps1 builds for each of its 30 runs. The
script also exits 0 once every rule has passed.

Reproduced with the runner's wrapper locally: cold cache, attempt 1
fails, attempt 2 restores, 30 validation runs pass, step exit 0.
@kieronlanning
kieronlanning merged commit 31812cc into main Nov 3, 2026
2 checks passed
@kieronlanning
kieronlanning deleted the feat/zodsharp-3-blazor branch November 3, 2026 00:51
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