# Description Fix five failures reproduced while exercising the recycling sample through a locally built OPC UA MCP server: - Support Certificate authentication through named, host-approved X.509 identity selectors and the existing certificate/identity providers. Tool arguments cannot supply private keys or arbitrary certificate-store paths. - Register custom enumeration dependencies from the validated, bounded type snapshot before loading requested structure roots. - Preserve structured fields and opaque encoded bodies in generic read/call results using the selected session's UA codecs. - Add bounded, explicitly typed UA JSON method arguments while retaining the existing string argument behavior and C# overloads. - Preserve omitted defaults in sparse recycling scenario JSON; explicit invalid values and unknown members still fail validation. All six shipped scenario files are covered by regressions. Update the MCP and recycling documentation. This PR contains one fix commit on a feature branch in **OPCFoundation/UA-.NETStandard**, based directly on upstream `master` at `95850b7e5`. No local registration, certificates, scenario journals or live-test artifacts are included. ### Validation **Final commit `d199feba0`: 216 affected MCP tests and 16 recycling CLI/scenario tests passed on .NET 10**, with no failures, fixture setup/teardown errors or build/analyzer warnings. The repository's TRX verdict helper accepted both reports. **Draft: the full-solution gate is not green.** The .NET 10 solution run encountered an `Opc.Ua.OneFuzz.Validator.Tests` fixture-publication failure outside the files changed by this PR: `MSB3030`, missing `Opc.Ua.OneFuzz.TestDependency.runtimeconfig.json`. It affected 210 tests; the run was stopped rather than expanding this PR into unrelated OneFuzz work. The full .NET Framework 4.8 solution suite was not run. Before the final rebase, the complete managed recycling suite passed **117**, and the CLI/scenario tests passed **16 each on .NET 8 and .NET 9**, with no failures or fixture teardown errors. The five explicit NativeAOT smoke cases were not selected. Actual live qualification used one coordinator and all ten independent process servers with strict certificate trust. Healthy, combined-fault and zero-utility runs each produced 24 complete packages with exact component conservation. Also exercised native companion discovery, subscriptions, authorization, pause/step/enable/disable, durable request replay, quality holds, reset and coordinator crash recovery. NativeAOT executables, container deployment and parity against a complete external producer contract were not qualified. <details> <summary>Exact validation commands and results</summary> Commands ran from the repository root with an isolated .NET SDK 10.0.401. `$root` denotes the private validation-artifact directory; no machine-specific path or key material is included here. Affected MCP matrix, final commit — **passed: 216; failed: 0; skipped: 0**: ```powershell dotnet test tests\Opc.Ua.Tools.Tests\Opc.Ua.Tools.Tests.csproj -c Release -f net10.0 -p:CustomTestTarget=net10.0 -p:CustomTargetFrameworks=net10.0 -p:UseSharedCompilation=false -m:1 -nr:false --no-restore --nologo -v:quiet --filter 'FullyQualifiedName~SchemaRemoteTests|FullyQualifiedName~OpcUaJsonHelperTests|FullyQualifiedName~MethodServiceToolsTests|FullyQualifiedName~ConvenienceToolsTests|FullyQualifiedName~AttributeServiceToolsTests|FullyQualifiedName~ViewServiceToolsTests|FullyQualifiedName~OpcUaSessionManagerTests|FullyQualifiedName~ConnectionToolsTests|FullyQualifiedName~McpHostBuilderTests|FullyQualifiedName~ConfigurationToolsTests' --logger 'trx;LogFileName=mcp-pr.trx' --results-directory "$root\pr-validation\focused" ``` .NET 10 CLI/scenario regressions, final commit — **passed: 16; failed: 0; skipped: 0**: ```powershell dotnet test tests\Opc.Ua.Recycling.Samples.Tests\Opc.Ua.Recycling.Samples.Tests.csproj -c Release -f net10.0 -p:CustomTestTarget=net10.0 -p:CustomTargetFrameworks=net10.0 -p:UseSharedCompilation=false -m:1 -nr:false --no-restore --nologo -v:quiet --filter 'FullyQualifiedName~RecyclingCommandLineTests' --logger 'trx;LogFileName=recycling-cli-pr.trx' --results-directory "$root\pr-validation\focused" ``` Complete managed recycling suite, before rebase — **passed: 117; failed: 0; skipped: 0** (five explicit NativeAOT cases not selected): ```powershell dotnet test tests\Opc.Ua.Recycling.Samples.Tests\Opc.Ua.Recycling.Samples.Tests.csproj -c Release -f net10.0 -p:CustomTestTarget=net10.0 -p:CustomTargetFrameworks=net10.0 -p:UseSharedCompilation=false -m:1 -nr:false --no-restore --nologo -v:quiet --logger 'trx;LogFileName=recycling-final.trx' --results-directory (Join-Path $root 'results') ``` .NET 8 CLI/scenario regressions, before rebase — **passed: 16; failed: 0; skipped: 0**: ```powershell dotnet test tests\Opc.Ua.Recycling.Samples.Tests\Opc.Ua.Recycling.Samples.Tests.csproj -c Release -f net8.0 -p:CustomTestTarget=net8.0 -p:CustomTargetFrameworks=net8.0 -p:UseSharedCompilation=false -m:1 -nr:false --nologo -v:quiet --filter 'FullyQualifiedName~RecyclingCommandLineTests' --logger 'trx;LogFileName=recycling-cli-net8.trx' --results-directory "$root\results" ``` .NET 9 CLI/scenario regressions, before rebase — **passed: 16; failed: 0; skipped: 0**: ```powershell dotnet test tests\Opc.Ua.Recycling.Samples.Tests\Opc.Ua.Recycling.Samples.Tests.csproj -c Release -f net9.0 -p:CustomTestTarget=net9.0 -p:CustomTargetFrameworks=net9.0 -p:UseSharedCompilation=false -m:1 -nr:false --nologo -v:quiet --filter 'FullyQualifiedName~RecyclingCommandLineTests' --logger 'trx;LogFileName=recycling-cli-net9.trx' --results-directory "$root\results" ``` Full .NET 10 solution — **not passed**: stopped after the OneFuzz fixture failure above; incomplete solution run, not a clean validation result: ```powershell dotnet test UA.slnx -c Release -p:CustomTestTarget=net10.0 -p:CustomTargetFrameworks=net10.0 -p:UseSharedCompilation=false -m:1 -nr:false --nologo -v:quiet --logger trx --results-directory "$root\pr-validation\net10" ``` </details> ## Related Issues Follow-up to [#4772](https://github.com/OPCFoundation/UA-.NETStandard/pull/4772), which introduced the recycling sample. No separate tracking issue is currently linked. These fixes preserve the existing architecture and address failures reproduced during live qualification. ## Checklist _Put an `x` in the boxes that apply. You can complete these step by step after opening the PR._ - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [ ] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. Regression tests were added and passed as recorded above; aggregate coverage was not collected, so the combined coverage checklist item remains unchecked. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: marcschier <11168470+marcschier@users.noreply.github.com>
Recycling line simulation
This sample models a recycling line with one durable coordinator and ten OPC UA process servers. The same executable starts either the coordinator or one selected process. It targets .NET 8, 9 and 10 and exposes typed process data, companion-specification equipment and material genealogy over OPC UA. It is a synthetic software simulation, not a controller for physical equipment; it includes no HMI, MQTT publisher or WoT connector.
Contents
- How the sample fits together
- What the servers expose
- Run the sample
- Observe and control the line
- Tests and publishing
How the sample fits together
Program.cs loads the role, scenario and certificate mappings.
RecyclingHost.Build assembles the Generic Host, secure endpoint and dependency-injected
runtime. The coordinator alone owns RecyclingEngine, MaterialLedger and RecyclingJournal.
Each process server uses CompanionProcessViewAdapter to project committed snapshots onto its
equipment: it has no material clock, independent random signals or production counters.
The sample model supplies generated structures, methods,
clients and DI-derived process types in urn:opcfoundation.org:sample:recycling. Native
companion instances and Machinery state AddIns share one DI/Machinery owner; AMB registers
the actual assets. The implementation is divided into:
| Directory | Responsibility |
|---|---|
| Coordination | Authenticated membership, serialized commands, committed snapshots and typed client access. |
| Simulation | Deterministic transformations, finite buffers, utilities, genealogy and holds. |
| Persistence | Checksummed journal/checkpoint storage through the existing file-system provider. |
| Hosting | Role startup, authorization, companion assembly and read-only equipment projections. |
Material, time and faults
collection → sorting → shredding → washing → density separation
→ drying → extrusion → melt filtration → pelletizing → bagging
These ten selections contain 21 separately queued substeps. For example, washing contains prewash, friction wash, hotwash and rinse; drying separates mechanical dewatering, thermal drying and buffering.
A fresh run waits for all ten distinct authenticated roles to register and acknowledge an initial snapshot. The coordinator then advances one logical tick per pacing interval:
- Downstream operations use previously committed inventory first. Newly received material cannot traverse another substep in that tick.
- Residence time, throughput, available buffer capacity and electrical/thermal supply determine which transformations can run. Disabled or unavailable equipment, jams, strand breaks and utility constraints freeze the affected residence progress.
- Transformations split or merge component-wise source contributions. The complete result is journaled before it becomes visible to clients or process views.
Defaults are a fixed seed of 104729, 30 logical seconds per tick, one wall-clock second
between automatic ticks, 24 source batches, 60 kg buffers, 6 kg/tick throughput, 4 kg complete
packages, and one to four residence ticks. Each source batch contains 5 kg dry material plus
0.25 kg water. These settings are in
RecyclingConfiguration.
Mass is counted in integer milligrams, separately for natural polymer, colored polymer, dense polymer, labels, dirt and water. Every transition checks the following balance globally, per source, per substep and per split/merge:
source inputs + utility additions
= active inventory + complete packages + rejects/wastewater + evaporation
Optical sorting removes contaminants; washes add tracked water and remove dirt/labels. Density separation rejects dense polymer while retaining natural and colored polymer equally. Mechanical drying drains water; thermal drying and extrusion evaporate it. Filtration removes particles, not dissolved color. Screening removes proportional fines. Bagging merges contributions into exact full packages and retains any incomplete package as inventory.
The named scenarios are healthy, sorter-drift,
utility-disturbance, equipment-faults, combined and recovery. Fault windows use logical
ticks. Jam/strand-break counts are absolute onset counters, not counters incremented by reads.
Source zones, synthetic hotwash pH and package RGB also belong to committed coordinator state.
Holds and recovery
Equipment execution, data availability and material disposition are separate. Color/moisture holds follow descendants and merged packages; recovering equipment does not release material. An authorized operator or administrator explicitly releases or rejects a hold. Produced-package count can therefore differ from released-product count.
The coordinator writes complete state and accepted command outcomes before acknowledging them. Requests carry line/run identity, expected revision and a caller-owned request ID. Identical control-command retries return the original outcome, including after restart; conflicting reuse or stale revisions are rejected. Reconnection and repeated projections do not duplicate material.
The journal uses length-delimited SHA-256 frames, atomic same-directory publication and an
exclusive writer.lock. Checkpoints precede removal of superseded generations. Recovery checks
the chain, configuration and embedded model, and refuses corrupt/incompatible state rather than
starting an empty replacement run. A valid commit can recover past an interrupted .pending
write. Storage is single-writer process-crash recovery, not distributed consensus or a universal
power-loss guarantee. The retained schema is version 2; schema-1 stores are rejected.
A paused run stays paused after recovery. Pausing freezes material time but not membership, administration or disposition. Old session leases are invalidated on restart; participants re-register without resetting production. The default lease lasts 30 wall-clock seconds, and loss of a process eventually blocks its flow while upstream buffers remain finite.
Default bounds are 256 active parcels, 4,096 retained records per material/history category,
4,096 control receipts, 64 admitted writer calls, 32 local observers and 10 administrative
steps per command. Heartbeats retain only their latest durable outcome per process, separately
from control receipts. Exhausted material retention enters ResourceLimited; a full writer
queue refuses the call and automatic pacing retries on the next interval. Frames are capped
at 64 MiB and UA messages at 16 MiB.
What the servers expose
Coordinator.State is a structured SnapshotDataType, not JSON in a UA string. It contains
run/lifecycle identity, revision, logical tick/time, stages, parcels, complete packages, sinks,
source contributions, holds, execution history, participant leases and electrical/thermal energy.
ProcessView.State exposes the same committed snapshot alongside Process, LineId,
Available and CompanionBindingsComplete.
Every sample stage also exposes RunState, Available, Timestamp, MachineId, Process, RunId,
Revision, Tick, Stage, Material, Contributions, SourceIds, SourceBinId, SourceZone,
LotId, Executions and ThroughputKgHr. Mixed lots retain every contribution; SourceZone
reports mixed while the contribution records preserve individual zone labels.
Companion assets and process data
Each stage is discoverable from ProcessView, DI DeviceSet and Machinery Machines.
AMB's AssetsByProductInstanceUri aliases identify the actual equipment. Machinery identification
uses urn:recycling-sample:<line-id>:<asset-name>. Instance NodeIds are stable URI-qualified
containment paths, independent of run IDs and numeric namespace-table positions.
The table lists the process selections, default endpoint ports and principal live bindings. The sample namespace carries process-specific measurements alongside the native companion objects.
| Selection / port | Equipment and substeps | Exposed measurements and state |
|---|---|---|
collection / 62571 |
Collection bin and transport; native VehicleScaleType. |
Inventory, source contributions/zones and vehicle weight in kg. |
sorting / 62572 |
Inbound identification, feed conveyor, two optical stations; SimpleScaleType, Part 1 VisionSystemType, Part 2 VisionSystemAssetType and identified cameras. |
BeltSpeedMps, ThroughputTph, JamAlarm/JamEvents, ClassificationAccuracyPct, ContaminationPpm, DriftScore, CameraHealth and native vision results. |
shredding / 62573 |
Primary reduction and secondary granulation with DI/Machinery state. | MotorLoadPct, FeedRateKgMin, FlakeSizeAvgMm, ThroughputKgHr and jam flags/counters. |
washing / 62574 |
Prewash, friction wash, hotwash and rinse; native circulation/drain PumpType instruments. |
Pump status, TurbidityNtu, WaterTempC, FaultState, WashEffectivenessPct, MotorLoadPct, BathTempC, Ph, AdhesiveRemovalPct. |
density-separation / 62575 |
Float/sink process and native circulation pump. | CirculationPumpStatus, SeparationPurityPct, HdpeRecoveryPct (a target-polymer recovery proxy). |
drying / 62576 |
Mechanical dewatering, native OPC 40082-6 GranulateDryingDeviceType with bin/desiccant bed, then dry-material buffer. |
OutletMoisturePct, throughput, native temperature, airflow, dewpoint, residence durations, power and energy. |
extrusion / 62577 |
Native OPC 40084 ExtrusionLine_InterfaceType, Extruder_InterfaceType and temperature zones. |
Melt temperature/pressure, zone actual/set values, throughput and sample ExtrusionStabilityScore. |
melt-filtration / 62578 |
Native OPC 40084-6 Filter_InterfaceType, FilterPackageType, ScreenPackageType and temperature zones. |
Mesh fineness, filter status; sample AlarmState, DiffPressureBar, CloggingPct, MeltCleanlinessIndex. |
pelletizing / 62579 |
Native OPC 40084-8 Pelletizer_InterfaceType, drive, knives and die plate; pellet screening. |
Native drive speed, sample PelletSizeAvgMm, throughput, FaultState, StrandBreakAlarm/StrandBreakEvents. |
bagging / 62580 |
BaggingStationType, native AutomaticFillingScaleType and PackML state. |
BagWeightKg, PackageCount, BagsPerHr, LotId, ColourR, ColourG, ColourB, QualityClassification and absolute weighing statistics. |
Vision's latest result contains typed ExecutionDataType content, a run/execution JobId
and IsSimulated=true; reprojecting it does not publish a new inspection. Mechanical dewatering
remains a separate process, not a desiccant dryer. Filter differential pressure is a sample
measurement because the standard screen-package type has no such member.
The package models are described in Machinery, Vision, Pumps, Scales, AMB, Extrusion and Plastics/rubber, including granulate drying.
Units, timestamps and quality
- Numeric sample signals use
SyntheticMeasurementType(AnalogUnitRangeType) withEngineeringUnits,EURange,SemanticFieldandFormula. Weights are kg; native extrusion throughput is kg/h, pressure bar, mesh fineness micrometres and pelletizer speed rpm. Dryer durations are milliseconds, electrical energy kWh, bin airflow m³/h and bed airflow l/s. - Rates are run-to-date averages, not instantaneous flow. Removal, purity, recovery and
moisture derive from cumulative committed material.
Energyexposes separate electrical and thermal joule totals divided by packaged dry mass, including held packages; a zero denominator setsIsApplicable=false. - pH defaults to a committed synthetic 8.5 set-point observation. RGB is the floored
polymer-mass-weighted mix of configured natural/colored anchors, not calibrated colorimetry.
Their individual source timestamps survive subsequent commits and reconnects.
Other display coefficients are in
RecyclingSignalDefaultsandRecyclingNativeProcessOptions. - Views publish atomic images with coordinator-owned values and source times. On connection loss,
Available=false; present equipment readings retain their last values/time withUncertainLastUsableValue, while the root snapshot reports unavailable status. Missing observations or undefined ratios reportBadNoData, not fabricated zero readings. - A
GoodDataValue means usable data, not accepted product.CompanionBindingsComplete=trueidentifies this selected read-only equipment binding, not optional remote controls or formal conformance. Signals and weighing are synthetic, not calibrated/legal-for-trade measurements or a reproduction of an external producer schema.
Run the sample
Secure identities
The host uses encrypted/signed OPC UA sessions, RSA-SHA256 application certificates and X.509 user authentication. Anonymous access and automatic trust of unknown peers are disabled. Each role has a separate application PKI root; each process view also has a distinct user certificate and private key. Certificate provisioning uses the stack's certificate manager and certificate stores.
Application public certificates go into peer trust; user public certificates go into user trust. The external role map authorizes already authenticated user certificates:
{
"identities": [
{
"certificateSha256": "<64 hexadecimal SHA-256 digits of the DER user certificate>",
"identity": "collection-view",
"access": "ProcessView",
"process": "Collection"
}
]
}
Replace the placeholder and include one distinct ProcessView identity per process, plus any
Observer, Operator and SimulatorAdministrator users. The mapping contains fingerprints,
not keys. The application URI is urn:localhost:OPCFoundation:Recycling-<line-id>-<role>;
the certificate's names must match the advertised --endpoint.
One shared scenario
All eleven roles use the same effective configuration and embedded sample model. Each
executable independently defaults to healthy, so another scenario must be passed to both
the coordinator and all ten views. For --scenario <file.json>, every role needs a readable
copy with identical effective settings; container copies are mounted read-only.
JSON scenario files override the documented top-level defaults: omitted fields retain their defaults, while explicit zero values, nulls and unknown members still undergo normal validation. The checked-in scenario files have the same effective settings as their corresponding named scenarios.
Views compare the full configuration/model hash before membership or projection. It includes
residence, seconds per tick, source settings, fault schedules, leases and bounds—not just the
scenario name. A mismatch or missing identity reports BadConfigurationError and stops automatic
reconnect, retaining the last compatible image as unavailable. Correcting the view's inputs/build
and restarting it preserves the coordinator's retained state. Host endpoints, certificate stores
and --pace-ms may differ; they are outside the simulation identity.
Build and start the roles
Build with the repository's .NET 10 SDK. For managed builds and runs, select net8.0,
net9.0 or net10.0 and install the corresponding runtime. The examples select net10.0;
substitute your choice in both -f and CustomTestTarget for the managed commands.
From the repository root:
dotnet build samples\Recycling\RecyclingServer\RecyclingServer.csproj -f net10.0 -p:CustomTestTarget=net10.0
Start each role in a separate terminal from samples\Recycling\RecyclingServer, where
the default .recycling directory is ignored by Git. The following assumes provisioned
roles.json, application PKI stores and user stores; explicit --pki-root and --state-root
can select external locations.
dotnet run -f net10.0 --no-build -- --role coordinator --line-id line-1 --scenario healthy --identities .\roles.json
The coordinator listens on opc.tcp://localhost:62570/Recycling. Start a view with its own
user store and subject, then repeat for every selection/port in the equipment table:
dotnet run -f net10.0 --no-build -- --process collection --line-id line-1 --scenario healthy `
--coordinator opc.tcp://localhost:62570/Recycling --identities .\roles.json `
--identity-store .\user-stores\collection --identity-subject CN=CollectionView
--pace-ms changes wall-clock pacing, not logical seconds per tick. Views may start before the
coordinator; production starts only after all ten initialize. --help lists the flags.
Docker Compose and Kubernetes examples run the same eleven
roles with isolated storage; their single Docker image uses the managed .NET 10 target.
Observe and control the line
- Connect an OPC UA client with a trusted application certificate and mapped Observer user certificate. Browse namespace URIs rather than assuming numeric namespace indexes.
- Subscribe to
Coordinator.State,ProcessView.Stateor individual equipment measurements. CompareRunId,Revision,Tick,Availableand source timestamps: heartbeat/control commits can advance revision without advancing material time. - Follow
Parcels,Packages[].Material.Contributions,HoldsandHistoryto trace material.ReadGenealogy(afterExecutionId, maximumResults)returns pages of up to 128 executions;ReadSnapshot()returns the current typed image. - Observe
sorter-driftthrough classification, contamination and inherited holds;utility-disturbancethrough constrained stages/residence queues; andequipment-faultsthrough jam/strand-break state and absolute counters. Therecoveryscenario restores equipment conditions without implicitly releasing product holds.
The generated clients are RecyclingCoordinatorTypeClient and RecyclingProcessViewTypeClient.
RecyclingCoordinatorClient wraps a managed session with typed calls and subscriptions. For an
already authenticated observer session:
await using var client = new RecyclingCoordinatorClient(session, telemetry);
await foreach (CoordinatorNotification update in client.WatchAsync(cancellationToken))
{
CommittedSnapshot state = update.Snapshot;
Console.WriteLine(
$"Available={update.IsAvailable}, tick={state.Tick}, packages={state.Packages.Count}");
}
The coordinator's typed Execute method accepts operator/admin enable/disable and hold-disposition
commands, plus administrator pause/resume/stop/reset/step/checkpoint commands. Stepping is
paused-only; reset requires an explicit stop and is not recovery. Retries preserve the original
request ID and payload. Native production writes/methods are refused; vision result retrieval
remains available. ProcessView.ForwardCommand is limited to its own process's enable/disable
commands and defaults to BadNotSupported; the optional forwarder preserves the originating
end-user certificate, never elevating the process service identity.
Tests and publishing
Managed targets
The NUnit project covers deterministic conservation, genealogy, faults, authorization, configuration agreement, bounded queues, crash recovery and encrypted OPC UA subscriptions. From the repository root, run each managed target separately:
$tests = 'tests\Opc.Ua.Recycling.Samples.Tests\Opc.Ua.Recycling.Samples.Tests.csproj'
foreach ($framework in 'net8.0', 'net9.0', 'net10.0') {
dotnet test $tests -f $framework -p:CustomTestTarget=$framework -m:1
if ($LASTEXITCODE -ne 0) { throw "Tests failed for $framework" }
}
CustomTestTarget keeps referenced stack projects on the same framework. It makes a legacy
net48 solution leg an empty shell, not a fourth supported runtime. Managed publication also
selects a framework explicitly:
dotnet publish samples\Recycling\RecyclingServer\RecyclingServer.csproj -c Release `
-f net8.0 -p:CustomTestTarget=net8.0 -p:PublishAot=false
Native smoke profile
The explicit NativeAOT smoke profile remains net10.0. It launches a published coordinator and a collection/drying/extrusion/melt-filtration/pelletizing view, checks secure typed values and discovery, then restarts the coordinator to exercise paused recovery and durable retries. Publication requires the selected platform's native toolchain:
dotnet publish samples\Recycling\RecyclingServer\RecyclingServer.csproj -c Release `
-f net10.0 -r win-x64 -p:CustomTestTarget=net10.0 `
-o artifacts\recycling-native\published
$env:OPCUA_RECYCLING_NATIVE_EXE = (Resolve-Path artifacts\recycling-native\published\RecyclingServer.exe).Path
dotnet test tests\Opc.Ua.Recycling.Samples.Tests\Opc.Ua.Recycling.Samples.Tests.csproj `
-c Release -f net10.0 -p:CustomTestTarget=net10.0 `
--filter FullyQualifiedName~NativeCoordinatorAndProcessViewSupportSecureTypedCallsAndDurableRecovery
The project retains AOT compatibility analysis and enables native publication when a runtime identifier is supplied. The smoke cases are excluded from net8.0/net9.0 test assemblies and remain opt-in on net10.0; ordinary managed tests do not launch native executables or containers.