Devolutions.Now.Policy.Client
2026.9.3
Prefix Reserved
dotnet add package Devolutions.Now.Policy.Client --version 2026.9.3
NuGet\Install-Package Devolutions.Now.Policy.Client -Version 2026.9.3
<PackageReference Include="Devolutions.Now.Policy.Client" Version="2026.9.3" />
<PackageVersion Include="Devolutions.Now.Policy.Client" Version="2026.9.3" />
<PackageReference Include="Devolutions.Now.Policy.Client" />
paket add Devolutions.Now.Policy.Client --version 2026.9.3
#r "nuget: Devolutions.Now.Policy.Client, 2026.9.3"
#:package Devolutions.Now.Policy.Client@2026.9.3
#addin nuget:?package=Devolutions.Now.Policy.Client&version=2026.9.3
#tool nuget:?package=Devolutions.Now.Policy.Client&version=2026.9.3
Devolutions NOW package broker client for .NET
Devolutions.Now.Policy.Client contains .NET client logic for communicating with a Devolutions NOW package broker. It builds on Devolutions.Now.Policy.Api DTOs and implements the client-side HTTP-over-named-pipe transport.
Purpose
This package is the .NET transport/client layer for the package broker API. It does not define the protocol schema itself and does not execute package-manager operations locally. Instead, it serializes API DTOs, sends them to a broker, and deserializes broker responses.
The client is used to:
- discover whether a local broker is reachable;
- query broker health and capabilities;
- evaluate package operations without executing them;
- submit package operations for elevated execution;
- poll asynchronous operation status until completion or failure.
Execution responses additionally return a per-operation event channel descriptor (OperationSubmission.EventChannel) whenever the broker supports event channels. The channel carries the NOW_BROKER frame protocol: status change notifications pushed by the broker and, when the operation was submitted with CaptureOutput, stdout/stderr data. The frame codec (EventFrame, EventFrameDecoder) lives in Devolutions.Now.Policy.Api; see policies/docs/event-channel-protocol.md for the wire specification.
To consume the channel, pass the execution response to BrokerClient.OpenEventChannel; it connects to the advertised local pipe and returns an OperationEventChannel:
var execution = await client.Execute(request);
await using var channel = await client.OpenEventChannel(execution);
await foreach (var frame in channel.ReadEvents())
{
switch (frame)
{
case EventFrame.Stdout stdout: Console.Out.Write(stdout.Data); break;
case EventFrame.Stderr stderr: Console.Error.Write(stderr.Data); break;
case EventFrame.StatusUpdated: /* issue QueryStatus */ break;
case EventFrame.Finish: /* operation finished; enumeration completes */ break;
}
}
ReadEvents skips unknown frame kinds and completes after Finish or when the broker closes the channel; ReadFrame exposes the raw frame stream, including EventFrame.Unknown.
Architecture
The main surface is BrokerClient:
IsAvailableprobes the health endpoint.GetHealthandGetCapabilitiesquery broker metadata.GetPolicysendsGET /v1/policyand returns aPolicyResponsecontaining the active parsedPolicyDocumentafter strict validation of the successful response.GetPolicyManagementgets the atomic active/missing/invalid management snapshot and advisory write capability.ValidatePolicypreserves rawJsonElementdraft content for authoritative validation and returns a canonical draft, exact findings, and receipt.ReplacePolicyperforms a token- and receipt-bound optimistic replacement; confirmed overwrite still targets an exact newly observed token and is never unconditional.EvaluatesendsPOST /v1/package-operations/evaluate.ExecutesendsPOST /v1/package-operations/execute.ExecuteAndWaitsubmits an operation and polls status until a terminal state.QueryStatussendsPOST /v1/package-operations/get-status.CancelsendsPOST /v1/package-operations/cancelto request cancelation of an in-flight operation.OpenEventChannelconnects to the per-operation event channel advertised in anExecutionResponseand returns anOperationEventChannelframe reader.
Transport is abstracted behind IBrokerTransport, which exchanges HTTP-style BrokerTransportRequest and BrokerTransportResponse values. NamedPipeBrokerTransport is the default implementation and sends HTTP/1.1 over a Windows named pipe. Tests and future transports can inject their own transport through BrokerClientOptions.Transport.
Client context
BrokerClient owns the client-controlled context fields sent in PackageRequest.Client and StatusRequest.Client.
Callers provide the fields that cannot be derived reliably:
var client = new BrokerClient(new BrokerClientOptions
{
RequestedElevation = Elevation.Elevated,
});
The client fills the remaining context implicitly:
Transportis taken from the configuredIBrokerTransport.EffectiveUserdefaults to the current user and can be overridden throughBrokerClientOptions.EffectiveUser.ClientVersiondefaults to theDevolutions.Now.Policy.Clientassembly version.ClientExecutablePathdefaults to the current process path and can be overridden throughBrokerClientOptions.ClientExecutablePath.
The public client methods accept client-facing wrapper types instead of raw wire DTOs:
PackageOperationRequestomitsClientContextand lets the client fill it.OperationStatusQueryomitsClientContextand only requires the operation id.OperationCancelQueryomitsClientContextand only requires the operation id.
For transport-independent message identification, request DTOs serialize fixed RequestKind
discriminators automatically while the client fills RequestVersion at the top level of
PackageRequest and StatusRequest. Responses carry fixed top-level ResponseKind discriminators
and ResponseVersion; this is required for further protocol evolution and allows the client to
switch transport from HTTP to other mechanisms without changing the wire schema.
Before sending package operation and status requests, the client implicitly queries GetCapabilities once and caches the result. The cached capabilities are used as a local preflight gate: unsupported transports, package managers, operations, scopes, architectures, request body sizes, custom parameters, custom install locations, or captured output requests fail before the client sends the operation/status request. Use CapabilitiesResponse.SupportsManager(ManagerName) or GetManagerCapability(ManagerName) to check package manager support ahead of time.
Policy validation and replacement use the separate fixed BrokerApi.MaxPolicyManagementBodyBytes
limit (16 MiB / 16,777,216 bytes). BrokerClient measures the serialized UTF-8 request body,
including the complete validation or replacement envelope, before sending it. Transport helpers
must apply the same full-body limit to POST /v1/policy/validate and PUT /v1/policy only; package
operation requests retain their advertised 256 KiB default. The 16 MiB value is an operational cap
for realistic policies within the 1,024-rule editor model, not the schema's pathological theoretical
maximum.
Before sending package operation requests, the client fills missing request metadata:
RequestIdis generated withBrokerClient.GenerateRequestId()when empty. Request IDs are normalized to lowercase dashed GUIDs without braces.CreatedAtis set toDateTimeOffset.UtcNowwhen left as the default value.
Error handling and diagnostics
Response-oriented methods return successful DTOs or throw BrokerClientException. The exception includes:
Kind, aBrokerClientErrorKindsuch asBrokerUnavailable,Timeout,BrokerError,InvalidResponse,InvalidRequest,UnsupportedCapability, orRequestTooLarge.Endpoint, when the failing broker endpoint is known.StatusCodeandBrokerError, when the broker returned a structuredErrorResponse.
IsAvailable remains a boolean probe and reports diagnostics through BrokerClient.Trace. Other methods do not silently convert failures into null.
GetPolicy preserves both legacy and structured unsupported-endpoint behavior. Old Agents may return an empty or non-JSON 404, which is exposed with StatusCode == 404 and no BrokerError. Rebuilt implementations may return a structured ErrorResponse with Code == NotFound. A supported Agent that cannot provide its active policy returns a structured non-404 error.
The policy management methods preserve the same ordinary 404 behavior when an older Agent does not expose a newer route. A structured StalePolicyStoreToken error carries the atomic current Management snapshot; use its exact store token for an explicitly confirmed overwrite retry. UnsafePolicyPath uses HTTP 409 because it represents the current storage/write-capability state rather than authentication or elevation. Configured .yaml, .yml, extensionless, and other non-JSON policy paths use PolicyReadOnlyReason.UnsupportedFormat and ErrorCode.UnsupportedPolicyFormat (HTTP 422).
Schema relationship
The client depends on Devolutions.Now.Policy.Api, whose DTOs are validated against the OpenAPI document generated from Rust:
policies\rust\now-policy-api\openapi\now-policy-api.yaml
Sample request and response documents are shared with the Rust server-template tests so the .NET client layer stays aligned with the same contract.
Validation
Useful targeted checks:
dotnet test policies\dotnet\Devolutions.Now.Policy.Client.Tests\Devolutions.Now.Policy.Client.Tests.csproj
dotnet format policies\dotnet\Devolutions.Now.Policy.slnx --verify-no-changes
Run the Rust OpenAPI generator before these checks when API model or route metadata changes.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Devolutions.Now.Policy.Api (>= 2026.9.3)
-
net9.0
- Devolutions.Now.Policy.Api (>= 2026.9.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.