Commit Graph
312 Commits
Author SHA1 Message Date
Marc 72e4a83f77 Bind native WebTransport management through authenticated UASC
Keep the Part 32 Services baseline separate. Forward the same-session management provider through the admission lease, enforce protected MSG correlation and refuse Services on native management. Cover framing, encrypted composition, profile selection and discovery with 71 new regression cases. Document remaining wire and native/browser qualification gaps.
2026-10-27 13:41:19 +01:00
Marc 41d4348afb Merge master into data channel branch 2026-10-27 12:40:13 +01:00
Marc 43bde775a2 Pin Data Channels documentation to updated draft 2026-10-27 12:29:21 +01:00
Marc f6c2977033 Separate context and transport initiator roles 2026-10-27 10:02:19 +01:00
Marc 609a535033 Route configured UADP discovery requests 2026-10-27 07:46:11 +01:00
Marc 80c16f32cd Reject invalid UADP discovery types 2026-10-27 07:21:44 +01:00
Marc 482cd34c89 Add UADP discovery subtype routing 2026-10-27 07:10:34 +01:00
Marc c9d910bb71 Reject ambiguous secured discovery routes 2026-10-27 06:59:17 +01:00
Marc 42b2986224 Cover publisher-only UADP discovery routing 2026-10-27 06:52:38 +01:00
Marc 2855b3f25a Correct peer STOP implementation status 2026-10-27 06:29:18 +01:00
Marc 0dfae76f41 Add host-dispatched UAFX endpoint lifecycle 2026-10-27 06:15:50 +01:00
Marc 1d6db20b1d Implement Data Channel PubSub and UAFX foundations 2026-10-26 21:44:12 +01:00
Wolfgang ColsmanandMarc Schier 3c5aa421d3 Client: server redundancy over Reverse Connect member connections (#4885)
# Description

## Depends on

This pull request depends on #4883 (application-supplied reverse
connection sources) and includes its commits. #4884 (ServiceLevel-driven
failover, member probing and read statuses) is already merged into
master. Current master and the concurrent dependency merges are
preserved without rewriting branch history.

Review the complete PR diff, not only the last commit. Master's
ManagedSession logging event IDs 32-35 are preserved; unused
reverse-connection cleanup uses event ID 36.

## Problem

With Reverse Connect, each member of a non-transparent redundant Server
set opens its own connections to the Client. The existing redundancy
path assumed dialed connections for endpoint resolution, ServiceLevel
probes and recovery, preventing a Client from using the members'
incoming connections consistently.

## Change

- `IReverseConnectionAvailability : IReverseConnectionSource` exposes
`GetAvailableEndpointUrls(string serverUri)`. `ReverseConnectManager`
advertises unaccepted held connections and excludes a connection as soon
as it is accepted, including inside the accepting callback.
- Optional `ServerRedundancyOptions.MemberConnections` selects the
reverse-connect member resolver. Endpoint matching preserves the
ServerUri, security mode, security policy and URL scheme checks.
- Member probing, failover and subsequent recovery use the member
source, including for a custom resolver or explicit target whose
endpoint does not set `ReverseConnect.Enabled`.
- The initial Server retains its authenticated endpoint and original
source, manager or outbound transport. A-to-B-to-A failover uses that
original path consistently for resolution, availability, probing and
recovery.
- Switching to another member waits for at least `PeerDiscoveryTimeout`,
including after the previous reconnect budget expires. The
reverse-source dependency's cancellation and exhausted-budget handling
are preserved.
- Without `MemberConnections`, existing dialed member behavior remains
unchanged.

## Tests

The real reference-server fixture covers source, manager and outbound
initial connections, each with marked and unmarked member endpoints. All
six combinations exercise member failover, transport closure and member
recovery, then handler-selected failback to the initial Server with
probing enabled. They assert readable ServiceLevel, fresh member
connection requests and no initial-Server waits through the member
source.

The manager lifecycle regression asserts that a held connection is
available before acceptance and unavailable inside the accepting
callback. Existing resolver, handler, reactive failover,
unavailable-member and reverse-source budget/cancellation tests remain
enabled.

## Repair Evidence, 2026-10-23

Local Windows developer checks with .NET SDK 10.0.402, Release/net10.0,
before the later concurrent master integration:

- Initial-source/member routing: 6 passed.
- Combined dependency budgets, routing and accepted availability: 15
passed.
- Affected client redundancy, resolver, handler and manager lifecycle
suites: 198 passed.
- Every listed run completed with 0 build warnings and 0 build errors.
- The dependency's WoT retained-input fixture repair passed the original
case and both injected-conflict cases (3 tests), plus 15 neighboring
retained-input and retry-boundary cases. It checks exactly one correct
input per bounded publication attempt and retains the exact-one
cold-recovery assertion. No production WoT behavior is changed by that
repair.

The newly integrated baseline is being checked separately; the PR
comments and GitHub checks report its results. These prior results do
not claim completion of current-head full CI.

## Earlier Evidence

The original PR description recorded these results for historical head
`dc471de5d`, not the current head: client builds on
net10.0/net9.0/net8.0/net48 with no warnings or errors; affected classes
107/107 on net10.0 and net48; Sessions 80/80 on net10.0; the full Client
project 3311 executed and passed out of 3315 total; changed-file
whitespace/style/analyzer checks clean. Those historical checks have not
been relabeled as current-head evidence.

## Operational Notes

ServiceLevel probing consumes member connections. Members must continue
opening connections while they are probed and recovered. A previously
resolved member remains selectable while its probes obtain connections;
a member whose probe cannot obtain a connection and reports none
available becomes unavailable. The initial Server's original transport
is not subject to the separate member-source availability filter.

## Checklist

- [x] I have signed the CLA and read CONTRIBUTING.
- [x] Regression tests cover the repaired behavior.
- [x] Reverse Connect documentation describes initial-source failback,
unmarked endpoints and accepted-connection availability.
- [ ] Current-head full solution tests on .NET Framework and .NET 10
have completed successfully.
- [ ] All required current-head CI checks and CodeQL checks have passed.
- [ ] All review threads have been addressed and reviewed.

---------

Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
2026-10-26 06:29:28 +00:00
Marc Schier 53e9e25717 Add explicit published WoT Binding 1.00 consumer interoperability (#4892)
# Description

Implement the approved published OPC 10101 v1.00 consumer- Baseline:
[OPC 10101 v1.00, published
2026-01-08](https://reference.opcfoundation.org/specs/OPC-10101/v1.00).
Reviewed plaintext SHA-256:
`4ec1170c1c33c63907e7810e6ab0537d62b55735bc2d1191ec7f57bbf9926d93`.
Later draft vocabulary is not normative 1.00 evidence.

- Explicit `WotBindingProcessingMode.PublishedOneZero` in
planner/extractor and MCP options. Current remains the default; no
heuristic selection from vocabulary/revision. Original TDs need no
invented revision annotation.
- Published numeric namespaces use original effective document context,
not the connected NamespaceArray or unrelated later planning context.
- Actions require action-level array-valued `uav:componentOf` (§7.1.3).
Native Core ownership/endpoint checks select one valid Object receiver;
a valid explicit form receiver can disambiguate. No retired scalar form
fallback.
- Native `InputArguments`/`OutputArguments` establish object-schema
argument ordering, DataTypes and ranks without later
`argumentLayout`/`fieldOrder` or JSON-order guesses. Native integer
widths constrain actual values too.
- Shared Method/receiver resolution pins the same pair for MCP
authorization and dispatch. Direct/captured channels retain
preflight/session checks.
- Planning uses the live source-routed resolver document alongside local
declarations from NodeSet import; serialized `resolvedFrom` markers and
local containment cannot manufacture source ownership authority.

Read/write/observe/default operations, property mapping annotations,
exact channel/auth/allOf constraints and external token-provider
applicability have version-qualified evidence. Documentation
distinguishes converter identifier options from runtime processing
selection. The [25-requirement traceability
ledger](https://github.com/OPCFoundation/UA-.NETStandard/blob/a24ed5319f099980e9e67d536c4268de24d3ae49/tests/Opc.Ua.Types.Tests/Wot/Assets/wot-binding-traceability.json)
includes concrete tests, assertion-level evidence and qualifications.
Locator completeness is not implementation completeness.

## Related Issues

- Approved interoperability follow-up to merged #4877; differential
excludes its safe receiver/provenance implementation.
- #4869 remains outside scope and is not closed.

## 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.
- [ ] 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.
2026-10-26 07:22:18 +01:00
Marc d3004c4084 Validate complete UAFX configuration before flow creation 2026-10-24 18:01:20 +02:00
Marc f9c2ddf142 Add pinned UAFX Part 81 generated model contracts 2026-10-24 17:14:25 +02:00
Marc 9ce8192ebe Cover UADP source isolation and rollover and align Data Channel guides 2026-10-24 16:25:51 +02:00
Marc c5718a0b40 Merge master preserving Data Channel routing and metadata isolation 2026-10-24 15:58:53 +02:00
Marc cd1b3bcaa2 Implement revised Data Channel bindings and address PR feedback 2026-10-24 15:35:52 +02:00
Wolfgang ColsmanandMarc Schier f3980f9e72 Client: managed session recovery over application-supplied reverse connections (#4883)
# Description

## Problem

With Reverse Connect (OPC 10000-6, 7.1.3) the server opens the
transport connection and sends `ReverseHello`; the client never dials.
To
re-establish a lost session (OPC 10000-4, 6.7: a new SecureChannel, then
`ActivateSession` for the existing session, then republish or
`TransferSubscriptions`) the client therefore needs a fresh
server-initiated
connection for every recovery attempt.

A `ManagedSession` can obtain such connections only from a
`ReverseConnectManager`:

- The `ITransportWaitingConnection` passed to
`ManagedSession.CreateAsync` is
single-use and consumed by the initial connect. Without a manager every
later
recovery fails with `BadSecureChannelClosed` ("A fresh reverse
connection is
  required.") until the reconnect policy gives up.
- Recovery calls `ReverseConnectManager.WaitForConnectionAsync`
directly. The
manager owns its listeners and has no extension point, so connections an
  application accepts itself cannot be fed into it.

Applications that accept reverse connections themselves cannot use the
managed
recovery today. Typical reasons are admission decisions before the OPC
UA
handshake (source network, the server URI and endpoint URL from
`ReverseHello`), a bounded number of held connections per server, or
connections accepted by another process and handed over. Such an
application
has to drive `ManagedSession.ReconnectAsync(ITransportWaitingConnection,
...)`
itself and install a reconnect policy that refuses every attempt of the
managed
loop, which duplicates the backoff and budget of the managed loop and
races its
keep-alive based failure detection.

## API

```csharp
namespace Opc.Ua.Client
{
    public interface IReverseConnectionSource
    {
        ValueTask<ITransportWaitingConnection> WaitForConnectionAsync(
            Uri endpointUrl, string? serverUri, CancellationToken ct);
    }

    public class ReverseConnectManager : IDisposable, IAsyncDisposable, IReverseConnectionSource
    {
        // explicit implementation; delegates to WaitForConnectionAsync(Uri, string?, CancellationToken)
    }

    public sealed record ManagedSessionOptions
    {
        public IReverseConnectionSource? ReverseConnectionSource { get; init; }
    }

    public sealed class ManagedSessionBuilder
    {
        public ManagedSessionBuilder UseReverseConnect(IReverseConnectionSource source, Uri serverUri);
    }
}
```

## Behaviour

1. Initial connect: an explicit initial `connection` argument is still
used
first. Otherwise the session awaits the source (after the connect gate
admitted the attempt, like the manager path). When the endpoint is
refreshed
before connecting, discovery runs over one connection from the source
and the
   session over the next one, as with a manager.
2. Recovery: every place that awaits the manager during recovery
(reconnect,
in-place recreate, network-redundancy alternate, endpoint refresh,
explicit
`ReconnectAsync` without a connection, mirrored-session reactivation)
awaits
the source instead. A recovery attempt that runs under the reconnect
budget
waits at most for the remaining `MaxTotalReconnectTime`; when the budget
runs
out the source's token is cancelled and the attempt fails with
`BadTimeout`,
so the state machine leaves `Reconnecting` as it does after a failed
dial. A
source that throws ends that attempt like a failed dial; the next
attempt
   follows the policy's delay.
3. Precedence: `ManagedSession.CreateAsync(options, ...,
reverseConnectManager)`
with `options.ReverseConnectionSource` set throws `ArgumentException`.
The two
   `UseReverseConnect` builder overloads replace each other.
4. Ownership: the session closes a connection it received after the wait
was
cancelled (closing the session, disposal, budget exhausted), so a source
does
not have to track abandoned connections. A source that returns `null`
fails
the attempt with `BadUnexpectedError` instead of falling back to an
outbound
   connection.
5. Sessions without the option behave as before; the manager paths are
unchanged (the manager's initial connect still goes through the session
   factory's manager overload).

## Tests


`tests/Opc.Ua.Client.Tests/Session/ManagedSessionReverseConnectionSourceTests.cs`
(unit, no server):

- `CreateAsyncRejectsASourceCombinedWithAManagerAsync`
- `BuilderUseReverseConnectWithSourceSetsTheSourceAndServerUri`
- `BuilderUseReverseConnectWithAManagerReplacesTheSourceAsync`
- `BuilderUseReverseConnectRejectsNullArguments`
- `ReverseConnectManagerWaitsThroughTheSourceInterfaceAsync`
- `CancelledConnectCancelsTheSourceAndClosesALateConnectionAsync`


`tests/Opc.Ua.Sessions.Tests/ManagedSessionReverseConnectionSourceTests.cs`
(in-process reference server that keeps dialing the client's reverse
connect
listener; the source wraps that listener's `ReverseConnectManager` and
records
every call):

- `InitialConnectAwaitsTheSourceOnceAsync`
- `RecoveryReactivatesTheSessionOverAConnectionFromTheSourceAsync`:
closes the
  transport under the SecureChannel; the session reactivates the same
  `SessionId` over a new connection from the source, one source call per
attempt, and the subscription continues with higher sequence numbers
(not
  recreated).
- `EachRecoveryAttemptAwaitsTheSourceOnceFollowingThePolicyAsync`: the
source
  fails the first two recovery calls; exactly four calls, spaced by the
  policy's delay.
- `ASourceThatNeverYieldsEndsRecoveryWhenTheBudgetIsExhaustedAsync`: one
wait
  is cancelled when the 3 s budget is used up and the session reaches
  `Disconnected`.
- `DisposingWhileRecoveryWaitsCancelsTheSourceAsync`
- `ReverseConnectManagerRecoveryIsUnchangedAsync`: regression for the
manager
  path.

## Compatibility

Additive only: a new interface, a new init-only option, a new builder
overload
and an explicit interface implementation on `ReverseConnectManager`. No
existing signature changes. `IManagedSessionFactory` is unchanged;
callers of
`ConnectAsync(endpoint, configure)` can call `UseReverseConnect(source,
...)` in
`configure`.

## Related Issues

- No tracking issue; the problem and the design are described above.

## Checklist

- [x] 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.
- [x] 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. (Run
locally: the affected test classes on net48 and net10.0 and the whole
Opc.Ua.Client.Tests project on net10.0; not the whole UA.slnx.)
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.

---------

Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
2026-10-23 19:41:32 +00:00
Marc Schier 5d6c10a6e9 Align WoT projections with Binding and Connectivity specs (#4877)
# Description

Align WoT Binding, Connectivity, and required Projections support with
the merged official specification baseline.

Projection-local affordances use ordinary TD/TM conversion and Binding
planning. The canonical View retains its identity and organizes
converter-resolved local/imported Nodes without cloning imported Nodes.
Generated local identities are resource-scoped; authored identities are
preserved. Security/schema closure retains projection ownership. Counts
include local Binding-owned Nodes plus View-owned Nodes across dry-run,
commit, unchanged refresh and restart. Runtime configuration forwards
the converter-resolved active-root namespace for multi-model closures.

Recovery rejects absent/inconsistent committed canonical View evidence
rather than resetting publication. Local+imported recovery preserves
committed membership/digest when desired inputs have changed. Fixtures
use short isolated storage roots for net48/MAX_PATH.

Security names use exact UTF-8 percent encoding; dependency-support
names retain base64url. OPC UA forms with omitted content type select
the registered octet-stream codec. Historical fixture bytes remain
unchanged with explicit current derived expectations. UA Binary
serialization remains unsupported/unclaimed: the raw-byte/text codec is
not UA Binary.

Dependency observation disclosure checks management policy, source
group/exact-Version and every captured local target
origin/group/exact-Version. Existing native role/channel-restriction
validation is reused, including namespace defaults and the virtual role
hook. Denied/unavailable targets reject the entire value with generic
access failure, no partial graph or target-identity diagnostics. A
pruned target makes remote disclosure fail closed without erasing the
stored committed graph. Both committed snapshots and completed attempts
are tested. Explicit-node tests do not establish separate
namespace-default evidence. The current capture path generates local
registry pins, not arbitrary federated/provider targets; remote
authorization is outside this implemented capability and is not claimed.

## Related Issues

- Related to #4869 only historically. Performance work is explicitly out
of current scope; this PR does not fix or close it.

## 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.
- [x] 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.
2026-10-23 14:12:52 +02:00
Marc Schier 533937830b Restore UaLens Linux rendering (#4891)
# Description

Restore the Linux rendering checks temporarily excluded in #4822 by
updating all eight OpenUSD pins to the public **0.16.6-alpha** release.

The upstream fix keeps shared GPU surfaces where supported. When
Avalonia/Mesa cannot import external images or semaphores, it presents
completed Vulkan frames through host-memory readback instead. The
renderer, scene, and acceptance criteria remain unchanged.

- Update the complete OpenUSD package family and its verified-version
assertion together.
- Restore both live embedded-viewport workflow cases, the native
workflow smoke case, and the native viewport artifact case used by both
distribution jobs.
- Remove the temporary-exclusion notices and document the host-memory
presentation path and physical-GPU qualification limits.

All 21 resolved OpenUSD packages were independently verified on
nuget.org at source commit `0ebd61af4651457d903ed30f40965e8dcb060255`,
including the Metal and native RID graph. No private feed or replaced
DLLs are used. [Upstream
release](https://github.com/marcschier/openusd-dotnet/releases/tag/v0.16.6-alpha).

### Validation

The eight-file patch was carried unchanged onto `master` at
`97cc145811bc2d9cef11e685392696435b10df36`. The following Windows/.NET
10 checks were rerun on that base:

| Scope | Result |
| --- | --- |
| Live viewport and diagnostic workflows | Passed: 14, Failed: 0,
Skipped: 0 |
| OpenUSD package-version policy | Passed: 1, Failed: 0, Skipped: 0 |
| Viewport and rendered Vision adapters | Passed: 103, Failed: 0,
Skipped: 0 |
| Retained-camera provider and blank-frame guards | Passed: 45, Failed:
0, Skipped: 0 |

<details>
<summary>Windows commands and results</summary>

These options and reporting paths were used for each invocation:

```powershell
$options = @(
    '-c', 'Release', '-f', 'net10.0', '-p:CustomTestTarget=net10.0',
    '-p:UseArtifactsOutput=true',
    '-p:ArtifactsPath=D:\git\UA-.NETStandard_master\TestResults\ua-stack-feedback20261016',
    '-m:2'
)
$results = 'C:\Users\mschier\.copilot\session-state\abd33a2e-7139-44e1-9912-6e4451102b4e\files\openusd0166-pr-results'

# Passed: 14, Failed: 0, Skipped: 0
dotnet test tests\Opc.Ua.Lens.Workflow.Tests\Opc.Ua.Lens.Workflow.Tests.csproj @options --filter 'FullyQualifiedName~OpenUsdViewportWindowTests|FullyQualifiedName~DiagnosticCommandTests' --logger 'trx;LogFileName=workflow.trx' --results-directory $results

# Passed: 1, Failed: 0, Skipped: 0
dotnet test tests\Opc.Ua.Tools.Tests\Opc.Ua.Tools.Tests.csproj @options --filter 'FullyQualifiedName~OpenUsdPackageFamilyUsesOneVerifiedPrerelease' --logger 'trx;LogFileName=policy.trx' --results-directory $results

# Passed: 103, Failed: 0, Skipped: 0
dotnet test tests\Opc.Ua.Lens.Tests\Opc.Ua.Lens.Tests.csproj @options --filter 'FullyQualifiedName~OpenUsdViewport|FullyQualifiedName~RenderedVisionCaptureTests' --logger 'trx;LogFileName=adapters.trx' --results-directory $results

# Passed: 45, Failed: 0, Skipped: 0
dotnet test tests\Opc.Ua.Vision.Tests\Opc.Ua.Vision.Tests.csproj @options --filter 'FullyQualifiedName~RetainedSceneCameraCaptureProviderTests|FullyQualifiedName~BlankFrameGuardTests' --logger 'trx;LogFileName=capture.trx' --results-directory $results
```

The selected builds completed without new compiler/analyzer warnings.
`git diff --check` and the following formatting check passed:

```powershell
dotnet format whitespace --folder --include tests\Opc.Ua.Tools.Tests\PackageVersionPolicyTests.cs tests\Opc.Ua.Lens.Artifacts.Tests\PublishedArtifactTests.cs tests\Opc.Ua.Lens.Workflow.Tests\Companions\OpenUsdViewportWindowTests.cs tests\Opc.Ua.Lens.Workflow.Tests\Desktop\DiagnosticCommandTests.cs --verify-no-changes --verbosity quiet
```

</details>

The same patch was qualified before the base update on Ubuntu 24.04.5,
Mesa 25.2.8, and Xvfb, with no GPU device mounted:

| Scope | Result |
| --- | --- |
| Original live telemetry, picking, retained-camera capture, edit,
resize, and drain workflows | Passed: 14, Failed: 0, Skipped: 0 |
| Published Linux NativeAOT startup and native viewport | Passed: 2,
Failed: 0, Skipped: 0 |
| Freshly packed and installed Linux managed tool startup and native
viewport | Passed: 2, Failed: 0, Skipped: 0 |

Both artifact forms produced nonblank 256x192 Vulkan captures with
different decoded-pixel hashes after the scene edit, exit code zero, and
both required cleanup/smoke markers. No assertions, deadlines, or
workflow selectors were weakened.

<details>
<summary>Linux commands and results before the base update</summary>

The source was mounted read-only at `/repo`; isolated build outputs were
under `/work`, and retained results were under `/evidence`. These
commands ran in PowerShell inside the Ubuntu container:

```powershell
$options = @('-c', 'Release', '-f', 'net10.0', '-p:CustomTestTarget=net10.0', '-p:UseArtifactsOutput=true', '-p:ArtifactsPath=/work/build', '-m:2')
$env:LD_LIBRARY_PATH = '/work/build/bin/Opc.Ua.Lens.Workflow.Tests/release_net10.0:/work/build/bin/Opc.Ua.Lens.Workflow.Tests/release_net10.0/runtimes/linux-x64/native'

# Passed: 14, Failed: 0, Skipped: 0
xvfb-run --auto-servernum '--server-args=-screen 0 1600x1000x24 -dpi 96' dotnet test /repo/tests/Opc.Ua.Lens.Workflow.Tests/Opc.Ua.Lens.Workflow.Tests.csproj @options --filter 'FullyQualifiedName~OpenUsdViewportWindowTests|FullyQualifiedName~DiagnosticCommandTests' --logger 'trx;LogFileName=workflow-linux.trx' --results-directory /evidence/results -- NUnit.NumberOfTestWorkers=0 RunConfiguration.TreatNoTestsAsError=true

# Passed: NativeAOT publish, with trim/AOT diagnostics enabled
dotnet publish /repo/tools/Opc.Ua.Lens/Opc.Ua.Lens.csproj -c Release -f net10.0 -p:CustomTestTarget=net10.0 -p:UseArtifactsOutput=true -p:ArtifactsPath=/work/aotbuild -r linux-x64 --self-contained true -p:PackAsTool=false -p:PublishAotEnabled=true -p:SuppressTrimAnalysisWarnings=false -p:TrimmerSingleWarn=false -m:2 -o /work/native -bl:/evidence/native.binlog

$env:UALENS_ARTIFACT_EXECUTABLE = '/work/native/UaLens'
$env:UALENS_ARTIFACT_LOG_DIRECTORY = '/evidence/native-linux'
$env:UALENS_ARTIFACT_X11_VULKAN = 'true'
$env:UALENS_ARTIFACT_ALLOW_SOFTWARE_GL = 'false'
# Passed: 2, Failed: 0, Skipped: 0
xvfb-run --auto-servernum '--server-args=-screen 0 1600x1000x24 -dpi 96' dotnet test /repo/tests/Opc.Ua.Lens.Artifacts.Tests/Opc.Ua.Lens.Artifacts.Tests.csproj @options --filter 'FullyQualifiedName~PublishedArtifactTests' --logger 'trx;LogFileName=native-linux.trx' --results-directory /evidence/results -- NUnit.NumberOfTestWorkers=0 RunConfiguration.TreatNoTestsAsError=true

# Passed: restore, pack the selector and all three RID payloads, and isolated install
$pack = @('-p:CustomTestTarget=net10.0', '-p:UseArtifactsOutput=true', '-p:ArtifactsPath=/work/managedbuild', '-p:PublishAotEnabled=false', '-p:PublishAot=false', '-p:PublishTrimmed=false', '-p:PackAsTool=true')
dotnet restore /repo/tools/Opc.Ua.Lens/Opc.Ua.Lens.csproj -p:Configuration=Release @pack
dotnet pack /repo/tools/Opc.Ua.Lens/Opc.Ua.Lens.csproj -c Release --no-restore -p:TargetFramework=net10.0 @pack -m:2 -o /work/managed-packages -bl:/evidence/managed.binlog
dotnet tool install OPCFoundation.NetStandard.Opc.Ua.Lens --version 2.0.2-preview.100.g30f255be63 --tool-path /work/installed/tool --configfile /work/installed/NuGet.config --no-cache

$env:UALENS_ARTIFACT_EXECUTABLE = '/work/installed/tool/ualens'
$env:UALENS_ARTIFACT_LOG_DIRECTORY = '/evidence/installed-linux'
# Passed: 2, Failed: 0, Skipped: 0
xvfb-run --auto-servernum '--server-args=-screen 0 1600x1000x24 -dpi 96' dotnet test /repo/tests/Opc.Ua.Lens.Artifacts.Tests/Opc.Ua.Lens.Artifacts.Tests.csproj @options --filter 'FullyQualifiedName~PublishedArtifactTests' --logger 'trx;LogFileName=installed-linux.trx' --results-directory /evidence/results -- NUnit.NumberOfTestWorkers=0 RunConfiguration.TreatNoTestsAsError=true
```

The install configuration contained only the freshly packed
`/work/managed-packages` source; installation used an isolated tool
cache. The Linux SDK was 10.0.401, allowed by the repository's
minimum-SDK policy.

</details>

**Not run:** full `UA.slnx` suites on .NET Framework and .NET 10,
physical-GPU/display qualification, or downstream macOS/Windows
NativeAOT artifacts for this follow-up. Linux artifact results above
predate the move onto current `master`; the PR's CI must qualify the
final head. Coverage was not recollected, and no solution-wide or
current-head cross-platform CI pass is claimed.

## Related Issues

- Fixes #4890.
- Follow-up to #4822. No new application architecture or public UA API
is introduced; the compatibility repair is in the released OpenUSD
dependency.

## Checklist

- [ ] 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.

The existing rendering tests are restored, not replaced with weaker
cases. The remaining checklist items are not asserted by the focused
local evidence above.
2026-10-23 12:12:25 +02:00
Wolfgang ColsmanandMarc Schier be9ccf912c Client: evaluate the durable subscription lifetime on every physical create (#4882)
# Description

## Problem

`ISubscription.SetAsDurableAsync(TimeSpan lifetime, CancellationToken
ct)` marks a subscription durable with the Server method
`SetSubscriptionDurable` (OPC 10000-5, "SetSubscriptionDurable Method";
OPC 10000-4, "Durable Subscriptions"). `SetSubscriptionDurable` must be
called before the subscription has monitored items; a conforming server
rejects it afterwards with `Bad_InvalidState`.

`LogicalSubscription` stores the `TimeSpan` and installs an
`OnAfterCreateAsync` hook on each partition. The partition state machine
runs the hook after every `CreateSubscription` and before the first
`CreateMonitoredItems`, so durability survives every later physical
create:

- recreate after a service call reports `BadSubscriptionIdInvalid`
(`Subscription.ApplyStateChangesAsync` → `ResetToRecreateAsync`),
- recreate after `TransferSubscriptions` fails on session recreate
(`SubscriptionManager.RecreateSubscriptionsAsync` → `RecreateAsync`) or
on load (`RestoreTransferAsync` → `ResetToRecreateAsync`),
- recreate after a status-change timeout or an unsolicited
`Good_SubscriptionTransferred`,
- a new secondary partition (`TryApplyDurableToNewPartition`).

The hook always re-applies the lifetime fixed by the first call. An
application whose durable lifetime must not exceed a bound that shrinks
over time (for example an authorization or retention window that ends at
a known instant, after which the server must no longer queue
notifications) cannot keep that bound after a recreate, and it has no
way to supply a new value at the right moment:

- calling `SetAsDurableAsync` again after the recreate is rejected once
monitored items exist;
- `ISubscriptionNotificationHandler.OnSubscriptionStateChangedAsync` is
dispatched fire-and-forget, so a call made from it can arrive after the
first `CreateMonitoredItems`;
- `SubscriptionOptions` has no durable setting and `OnAfterCreateAsync`
is internal.

## API

```csharp
namespace Opc.Ua.Client.Subscriptions
{
    public interface ISubscription
    {
        // existing
        ValueTask<TimeSpan> SetAsDurableAsync(TimeSpan lifetime, CancellationToken ct = default);

        // new
        ValueTask<TimeSpan> SetAsDurableAsync(Func<TimeSpan?> lifetime, CancellationToken ct = default);
    }
}
```

## Behaviour

- The provider is evaluated once for the call itself; the value is sent
to every partition that is already created (unchanged: the call returns
the minimum revised lifetime of those partitions, or the requested value
when none is created yet).
- The provider is stored and evaluated again by the existing
after-create hook before every later physical create, between
`CreateSubscription` and the first `CreateMonitoredItems` of that
create.
- A `null` result requests no durability for that create: no
`SetSubscriptionDurable` is sent and the recreated subscription is a
regular one. A `null` result for the call itself returns `TimeSpan.Zero`
without contacting the server.
- An exception thrown by the provider during a later create is handled
exactly like a rejected `SetSubscriptionDurable` call today: the state
machine logs a warning, reports `SubscriptionState.Modified`, and the
subscription continues (its monitored items are created). An exception
during the call itself propagates to the caller.
- `SetAsDurableAsync(TimeSpan)` now forwards to `SetAsDurableAsync(() =>
lifetime)`; its behaviour is unchanged.
- `docs/Subscriptions.md` describes the provider and corrects the
statement that the hook is cleared after invocation (on master it stays
installed).

Not included: a notification reporting the outcome (revised lifetime or
status) of each re-applied `SetSubscriptionDurable`. Today a failed
re-apply is only logged and surfaced as `SubscriptionState.Modified`;
this could be a follow-up if maintainers want it.

## Tests

New tests in
`tests/Opc.Ua.Client.Tests/Subscription/SubscriptionTests.DurableLifetime.cs`
drive a real partition (`Subscription`) behind
`SubscriptionManager`/`LogicalSubscription` against mocked services and
record `CreateSubscription`, `SetSubscriptionDurable` (requested hours),
`CreateMonitoredItems`, `TransferSubscriptions` and
`DeleteSubscriptions` in call order. The provider returns 10 h for the
initial call and 7 h afterwards:

-
`DurableLifetimeProviderIsEvaluatedOnRecreateAfterSubscriptionIdInvalidAsync`:
`ModifySubscription` returns `BadSubscriptionIdInvalid`; the recreate
sends 7 h between `CreateSubscription` and the first
`CreateMonitoredItems`.
-
`DurableLifetimeProviderIsEvaluatedOnRecreateAfterFailedTransferAsync`:
`TransferSubscriptions` on session recreate returns
`BadSubscriptionIdInvalid`; delete, create, 7 h, items.
- `DurableLifetimeProviderIsEvaluatedForNewPartitionAsync`:
`MaxMonitoredItemsPerPartition = 1`; the second item mints a partition
that sends 7 h before its items.
-
`DurableLifetimeProviderReturningNullSendsNoSetSubscriptionDurableAsync`:
provider returns `null` on recreate; no `SetSubscriptionDurable`, items
are created.
-
`DurableLifetimeProviderThatThrowsLeavesRecreatedSubscriptionRunningAsync`:
provider throws on recreate; no `SetSubscriptionDurable`, items are
created, subscription stays created.
- `DurableFixedLifetimeIsReappliedOnRecreateAsync`: regression of the
`TimeSpan` overload (10 h again on recreate).

Unit tests in `LogicalSubscriptionTests`:
`SetAsDurableWithProviderEvaluatesItOnceForAllCreatedPartitionsAsync`,
`SetAsDurableWithProviderReturningNullSendsNothingAsync`,
`SetAsDurableWithNullProviderThrowsAsync`.

Negative control: with the hook evaluating the provider once at
installation (the previous fixed-value behaviour), the
recreate-after-`BadSubscriptionIdInvalid`, failed-transfer, null and
throwing-provider tests fail (4 of 10).

Local results (Windows, .NET SDK 10.0.402, Release):

| Run | Result |
| --- | --- |
| `Opc.Ua.Client` build, net48/net8.0/net9.0/net10.0 | 0 warnings, 0
errors |
| `SubscriptionTests` + `LogicalSubscriptionTests`, net10.0 | 105/105
passed |
| `SubscriptionTests` + `LogicalSubscriptionTests`, net48 | 105/105
passed |
| `Opc.Ua.Client.Tests`, namespace `Opc.Ua.Client.Subscriptions`,
net10.0 | 353/353 passed |
| `Opc.Ua.Subscriptions.Tests` `SubscriptionManagerCoverageTests` +
`StreamingSubscriptionCoverageTests`, net10.0 | 72/72 passed |
| `Opc.Ua.Subscriptions.Durable.Tests` `SubscriptionDurableTests` +
`DurableSubscriptionTest*` (in-process reference server with durable
subscriptions enabled), net10.0 | 21/21 passed |

## Related observation (not changed here)

A secondary partition minted by `SubscriptionManager.Add`'s partition
factory starts its state machine in the `Subscription` constructor,
before the factory calls `TryApplyDurableToNewPartition`. When
`CreateSubscription` completes before the factory returns (observed with
a synchronously completing mock), `RunAfterCreateHookAsync` finds no
hook and the new partition is created without `SetSubscriptionDurable`,
for the `TimeSpan` overload as well. With a real round trip the hook is
installed first;
`DurableLifetimeProviderIsEvaluatedForNewPartitionAsync` therefore holds
the secondary's `CreateSubscription` response until the factory has
returned. Installing the hook before the partition's first state-manager
pass would remove the window.

## Compatibility

- Source and binary compatible for callers of `ISubscription`.
- `ISubscription` gains a member. Custom implementations of the public
interface (outside the stack; the stack's own implementations are
internal) must add the overload; a default interface implementation is
not possible because the library also targets .NET Framework 4.8. The
three test doubles in the repository were updated.
- No wire, configuration or server change.

## Related Issues

- Independent of #4881; with #4881 the lifetime provider is also applied
to a new partition that is created before the partition factory returns.

## Checklist

- [x] 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.
- [x] 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. (Run
locally: the affected test classes on net48 and net10.0 and the whole
Opc.Ua.Client.Tests project on net10.0; not the whole UA.slnx.)
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.

---------

Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
2026-10-22 18:51:01 +02:00
Alexander Köpke fb4eece24d Fix OpenUSD sample discovery and migrate artifacts to xRegistry (#4880)
# Description

_Describe the changes here to communicate to the maintainers why they
should accept this pull request. By default - this will become the
Commit message after merging and thus define history._

Fix OpenUSD sample discovery and integrate artifact delivery with the
xRegistry companion model.

- Load xRegistry before OpenUSD in the sample node managers, mount
stages with `Organizes`, and align root-layer identifiers with served
`.usda` files.
- Bootstrap immutable logical Resources and exact Versions in
`Artifacts`. Stage `Assets` folders provide non-owning `Organizes`
views, and explicit container identifiers allow sharing.
- Update the binding model to draft 0.9.0 while preserving existing
NodeIds and the `HasAddIn` correction.
- Disambiguate default container identifiers by stage instance, publish
the required read-only capabilities map, and preserve standard and
xRegistry BrowseName namespaces.
- Add registry, file-delivery, sharing, collision, qualified-browse, and
live-discovery regressions. Update the OpenUSD documentation.

Review fixes are in 6ac47a5b82. All four
review threads have been answered and resolved.

**Local validation**

| Command | Result |
| --- | --- |
| `dotnet test tests/Opc.Ua.OpenUsd.Tests/Opc.Ua.OpenUsd.Tests.csproj
--no-restore -f net10.0 -p:CustomTestTarget=net10.0 --verbosity quiet` |
Passed: 1,178 tests. |
| `dotnet test tests/Opc.Ua.OpenUsd.Tests/Opc.Ua.OpenUsd.Tests.csproj -f
net48 -p:CustomTestTarget=net48 --filter
FullyQualifiedName~UsdAssetDeliveryTests --verbosity quiet` | Passed: 36
focused regressions, including build and dependency restore. |
| `dotnet test tests/Opc.Ua.OpenUsd.Tests/Opc.Ua.OpenUsd.Tests.csproj
--no-build --no-restore -f net48 -p:CustomTestTarget=net48 --verbosity
quiet` | Failed: 937 passed; five generator integration tests failed
during fixture setup because Mono does not implement
`ECDsa.Create(ECCurve)`. |

The full `UA.slnx` suite and coverage measurement were not run locally.

## Related Issues

_Reference all GitHub issues this PR addresses. If there is no issue
yet, open one and link it here._

_If this is a relatively large or complex change, a design must have
been discussed in the related tracking issue and signed off (which
becomes the Architectural Decision Record (ADR))._

No linked tracking issue.

## 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.
- [x] I have addressed **all** PR feedback received.
2026-10-22 12:45:21 +00:00
romanettandClaude Opus 5.5 df6adec1af Refresh server benchmarks for 2.0.2: GC modes, processor placement, delivery (#4868)
Closes #4842.

Measures the items that `docs/Benchmarks.md` listed under *Open
measurements* and makes every published table reproducible from
checked-in files.

All runs: `master` f6b27025a, AMD Ryzen AI 9 HX 370 (4 Zen 5 + 8 Zen 5c
cores, 24 logical), 64 GB, Windows 11 26300, Balanced power plan, .NET
SDK 10.0.402 / runtime 10.0.13. Each table has 5 rounds per variant,
alternated and reversed every other round, and reports the median with
its min–max range.

## Docs
- **Reference Read table** re-run with ranges. The 2026-10-18 single
runs move to a *Historical results* subsection.
- .NET pinned to 4 logical processors: 440k req/s at 8.9 µs CPU per
Read.
  - open62541: 137k req/s at 7.3 µs.
  - .NET on 24 logical processors: 600k req/s at 22.9 µs.
- **Garbage collector modes**: workstation, Server GC with DATAS, and
Server GC with fixed heaps, on `native-read`, `read` and `events`.
- Server GC gave 45–89 % more throughput than workstation GC.
Workstation GC paused the server for 30–46 % of the window.
- Turning DATAS off cut GC pauses 4.6–8× at 1.9–2.8× the working set,
with no throughput difference beyond the ranges.
- **Processor placement** on the hybrid CPU, with and without
SignAndEncrypt.
- Server on the Zen 5 logical processors (`0xFF`): median throughput
within 7 % of the default placement at about half the CPU per Read (11.7
vs 22.9 µs).
- **Publish and event delivery** on the merged 2.0.2 master: delivered
share, latency, CPU and allocation per notification, and GC pause.
- **Recommended baseline** now cites these measurements.
`ServerScalability.md` GC and affinity paragraphs are updated to match.
- **Open measurements** now lists only what is still unmeasured: Linux,
Intel hybrid CPUs, a dedicated host, and session establishment and
scaling.

## Regression found (documented, not fixed here)
The 50 sessions × 15 subscriptions × 600 items publish shape from #4829
now delivers only about 26 % of the expected notifications.

| Commit | Delivered | Server CPU / notification |
|---|--:|--:|
| `37a2a831f` (#4829 merge) | 93 % | 3.9 µs |
| `f6b27025a` (master) | 23 % | 46 µs |

Both rows use the same harness arguments. A `git bisect` finds 65a67dd2f
(#4757) as the first bad commit: its parent 0edc3b55a delivers 98 % at
2.8 µs, while 65a67dd2f delivers 6 % at 94 µs. The fix is being worked
on separately.

## Tooling
- `perf/variants.sh` runs one harness build under several
configurations, given as `name|env|args` lines. It discards and repeats
any run that overlaps another test host or load harness.
- `perf/variants-summary.ps1` prints the median and range of each metric
per variant.
- `perf/benchmarks/*.txt` holds the variant and scenario files for each
table.
- `perf/README.md` documents all of the above.

## Test plan
- [x] All four batches ran with the checked-in files. 2 of 140 runs were
discarded because of foreign load; none failed.
- [x] The summaries reproduce the tabulated numbers.
- [x] CRLF line endings preserved in the edited docs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 16:20:04 +00:00
6fe5399510 Add UaLens desktop engineering workspace (#4822)
# Description

Add **UaLens**, an Avalonia desktop engineering workspace for OPC UA
exploration, monitoring, typed editing, administration and guided
companion-specification tasks.

This is a **new draft PR for `pisrapp` against `master`**, replacing
closed, unmerged #4462 without reopening it. The shared prerequisites
(#4801, #4802, #4803, #4804 and #4806) have all merged; their
implementation is no longer part of this PR's delta.

### Scope

- Multi-document workspace with connection, security and identity
controls; browsing, subscriptions, events, alarms, history, writes and
method calls.
- Offline NodeSet2 exploration, typed scalar/array/matrix editing,
certificate/GDS and account/role tools.
- UaLens companion workflows, repository sample launching and the Aspire
AppHost.
- PubSub commissioning and capture/replay integration, plus the
UaLens-facing OpenUSD viewport and rendered Vision workflows.
- UaLens projects, test assemblies, UI assets, guides,
solution/dependency entries, desktop test wiring and delivery workflows.

Head **`6f46c51fcfef275059bbe77ef39c64c19ebc1053`** is one UaLens-only
commit directly above master
**`44a8cb277df450162c29313e2f4c862e7c683752`**. The rebase required no
conflicts. All **967 UaLens file blobs**, the full-index binary patch,
commit-message text and author/co-author attribution are unchanged from
the previously published layer. There is **no implementation delta under
`src/`**. The original history is retained in recovery refs, including
`refs/pr-stack/backups/master20261017/ualens`.

No feature was disabled, and no export/path safeguards, native
acceptance criteria, test assertions or CI gates were weakened. The four
previously recorded blank-line-at-EOF findings in the UaLens dialog
files remain unchanged; they are not reported as newly fixed.

### Validation

Fresh validation ran on the **exact PR head `6f46c51f`**, using the
existing pinned SDK **10.0.401**, normal repository analyzers and
physical/non-linked build outputs:

```powershell
$d = 'D:\git\UA-.NETStandard_master\TestResults\ualens-528-deep-repro\sdk\dotnet.exe'
$b = 'D:\git\UA-.NETStandard_master\TestResults\ua-stack-feedback20261016'
$r = 'C:\Users\mschier\.copilot\session-state\abd33a2e-7139-44e1-9912-6e4451102b4e\files\ualens-new-draft20261017-net10'
$env:DOTNET_ROOT = Split-Path -Parent $d
$env:DOTNET_ROOT_X64 = $env:DOTNET_ROOT
$env:DOTNET_CLI_TELEMETRY_OPTOUT = '1'
$env:PATH = $env:DOTNET_ROOT + [IO.Path]::PathSeparator + $env:PATH

& $d test 'tests\Opc.Ua.Lens.Tests\Opc.Ua.Lens.Tests.csproj' --no-restore --artifacts-path $b -c Release -f net10.0 -p:CustomTestTarget=net10.0 --filter 'TestCategory!=LongRunning&TestCategory!=Stress' --logger 'trx;LogFileName=ualens-new-draft.trx' --results-directory $r --blame-hang --blame-hang-timeout 3m --blame-hang-dump-type none --verbosity minimal
# PASS, exit0: Passed: 4524, Failed: 0, Skipped: 0.
```

The saved TRX contains **4524 Passed and 44 explicit/prerequisite
NotExecuted records** (4568 total). The repository verdict helper
confirms zero failed tests, fixture setup/teardown failures or run-level
errors, without toleration. No compiler/analyzer diagnostics were
emitted. Logs, TRX and `ualens-new-draft20261017-validation.json` retain
this exact-head evidence.

**Not run for this new draft:** the full `UA.slnx` net48/net10 matrix,
fresh coverage, other desktop TFMs, application-desktop/native workflow
suites, NativeAOT, GPU/per-RID/installed-runtime or package-release
qualification. UaLens targets .NET8+; no net48 UaLens execution is
claimed. Remote CI and CodeQL are not yet claimed green. Historical
native diagnostics and successful managed tests are not substitutes for
rendered-frame or release acceptance.

The previous PR and retained session artifacts preserve the full
extraction, review, failed-attempt and diagnostic history. Historical
intermittent CI failures were not silently reclassified as root-cause
fixes; no old diagnostic grants or producer/release operations were
reused for this PR.

## Related Issues

_Reference all GitHub issues this PR addresses. If there is no issue
yet, open one and link it here._

_If this is a relatively large or complex change, a design must have
been discussed in the related tracking issue and signed off (which
becomes the Architectural Decision Record (ADR))._

- References #4461, the existing closed UaLens design/qualification
tracking issue; no new design approval is implied.
- Replaces closed, unmerged #4462 for the remaining UaLens work.
- Builds on merged prerequisites #4801, #4802, #4803, #4804 and #4806;
this PR does not close them.

## 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.

The test/coverage item remains unchecked because fresh coverage was not
collected. This draft does not claim CLA confirmation, full-matrix
clearance, native qualification or completed review.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Alexander Köpke <alexander.koepke@microsoft.com>
Co-authored-by: Copilot <Copilot@users.noreply.github.com>
2026-10-21 17:18:19 +02:00
Alexander Köpke 4d00467f7e Add missing component to PumpType instances (#4867)
This pull request introduces improvements to how components and their
references are managed and exposed in both the Machine Vision and
Recycling sample domains. The main focus is on ensuring that components
use the correct `HasComponent` reference type, and that pumps expose
mandatory two-state status labels with appropriate properties and
descriptions. Additional tests are included to verify these behaviors.

**Component Reference Improvements:**

* Updated the `WithComponents` method in the Vision documentation and
implementation to use `HasComponent` references instead of `Organizes`,
ensuring that components are referenced in compliance with OPC UA
modeling best practices.
[[1]](diffhunk://#diff-c885bd41583e603a034705800d1cd03213b4f3c8b1e1611b31121652e6548c5aL340-R341)
[[2]](diffhunk://#diff-89611f4e76aef466943f80ed4079b1275d4ee2016610ad2407f7c8932f2d07e0L252-R252)
* Modified the Machine Vision asset builder and related tests to verify
that components are correctly referenced with `HasComponent`, and that
shared components are properly managed without reparenting or replacing.
[[1]](diffhunk://#diff-c265154f7bd0bbcbaa003aa050270e193e47ba941b41f9158a49f799efbcdf14L506-R517)
[[2]](diffhunk://#diff-c265154f7bd0bbcbaa003aa050270e193e47ba941b41f9158a49f799efbcdf14R528-R535)

**Pump Status Label Enhancements:**

* Refactored pump status variable initialization in the Recycling sample
to ensure that the mandatory two-state properties (`FalseState` and
`TrueState`) are set with the labels "Stopped" and "Running", and that a
descriptive text is provided.
* Updated the Recycling sample documentation to clarify that each pump's
status exposes the mandatory `FalseState` and `TrueState` properties as
"Stopped" and "Running", reflecting the synthetic pump state.

**Testing:**

* Added a comprehensive test to verify that every circulation and drain
pump exposes readable mandatory two-state status labels (`FalseState`
and `TrueState`) with the correct values and reference types.#
Description

_Describe the changes here to communicate to the maintainers why they
should accept this pull request. By default - this will become the
Commit message after merging and thus define history._

## Related Issues

_Reference all GitHub issues this PR addresses. If there is no issue
yet, open one and link it here._

_If this is a relatively large or complex change, a design must have
been discussed in the related tracking issue and signed off (which
becomes the Architectural Decision Record (ADR))._

- Fixes #github-issue-number, ...

## Checklist

_Put an `x` in the boxes that apply. You can complete these step by step
after opening the PR._

- [x] 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.
- [x] 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.
- [ ] 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.
2026-10-21 13:58:08 +00:00
f6b27025a6 Classify sensitive log parameters with Microsoft.Extensions.Compliance data classification (#4864)
## Proposed changes

Fixes #4857. The stack's log messages wrote network addresses (endpoint,
discovery and reverse connect URLs, host names, IP addresses, ports),
application and user identities, file paths and exception texts as plain
parameters. Applications had no reliable way to keep them out of
general-purpose logs.

This PR replaces the stack's own redaction mechanism with the standard
.NET [data classification and
redaction](https://learn.microsoft.com/dotnet/core/extensions/data-redaction)
packages. Each sensitive `[LoggerMessage]` parameter is classified. The
application decides per classification what reaches its logs: erase it,
replace it with an HMAC token so entries stay correlatable, or pass it
through.

### Taxonomy
`Opc.Ua.Redaction.OpcUaDataClassifications` defines the `OpcUa` taxonomy
and one attribute per classification:

| Classification | Used for | Parameters |
|---|---|---|
| `[NetworkAddress]` | URLs, host and domain names, IP/MAC addresses,
end points, ports | 120 |
| `[ApplicationIdentity]` | certificates, subject, issuer, thumbprint,
ApplicationUri | 103 |
| `[FilePath]` | file, directory and certificate store paths | 67 |
| `[SensitiveText]` | exception or remote-supplied text passed as a
parameter | 55 |
| `[UserIdentity]` | user names, token subjects | 7 |

Correlation ids (`ChannelId`, `SessionId`, ...), status codes and
NodeIds stay unclassified.

### Pipeline and defaults
- Opc.Ua.Core now references
`Microsoft.Extensions.Telemetry(.Abstractions)` 10.11.0, so every
project above Core uses the `Microsoft.Gen.Logging` generator.
Opc.Ua.Client already did, through Http.Resilience. The packages ship
net462 and netstandard2.0 builds, so net48 is supported.
- **A plain `ILoggerFactory` logs `(null)` in place of classified
values.** Classified values are only written by the redaction-aware
logger.
- The new `ILoggingBuilder.EnableOpcUaRedaction()` turns that logger on.
It is `EnableRedaction()` with the discriminator off; otherwise every
unredacted value gets `:ParamName` appended.
`AddOpcUa().AddLogging(...)` and `DefaultTelemetry.Create(...)` call it,
as do the samples, so they log the same information as before until
redactors are registered.
- `services.AddOpcUa()` alone does not change the host's logging.
Generic-host apps call `builder.Logging.EnableOpcUaRedaction()`. This is
documented as a behavior-breaking change in
`docs/migrate/2.0.x/telemetry.md`.

### Obsolete API
`Redact`, `RedactionWrapper<T>`, `IRedactionStrategy`,
`RedactionStrategies` and `SimpleRedactionStrategy` are `[Obsolete]`.
They still work, but the stack no longer uses them.

### Not classified
- The `Exception` passed to a log call. Applications can drop exception
messages with `LoggerEnrichmentOptions.IncludeExceptionMessage`.
- Exception and `ServiceResult` message texts.
- `ServiceResult`/`StatusCode` parameters and logged data values.
- The few log calls whose level is decided at run time.
- `Opc.Ua.Vision.OpenUsd`, which references only Opc.Ua.Types and cannot
see the attributes.

### Generator behavior differences
These are visible to log consumers and to tests:
- `IFormattable` values such as `NodeId` are stored in the structured
state as invariant strings, not as objects.
- The generated log state is a thread-local object that is cleared after
the call.
- Classified values are pooled redactor objects, valid only during the
`Log` call.

Tests that inspected log state after the fact now capture it during the
call. The shared `RecordingLoggerProvider` snapshots classified values.

## Related Issues
- Fixes #4857

## Types of changes
- [ ] Bugfix (non-breaking change which fixes an issue)
- [x] Enhancement (non-breaking change which adds functionality)
- [ ] Test enhancement (non-breaking change to increase test coverage)
- [x] Breaking change (fix or feature that would cause existing
functionality to not work as expected; plain logger factories drop
classified values)
- [x] Documentation Update (if none of the other choices apply)

## Checklist
- [x] I have read the CONTRIBUTING doc.
- [x] I have signed the CLA.
- [x] I ran tests locally with my changes, all passed.
- [x] I fixed all failing tests in the CI pipelines.
- [x] I fixed all introduced issues with CodeQL and LGTM.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added necessary documentation (if appropriate).
- [ ] Any dependent changes have been merged and published in downstream
modules.

## Further comments
New tests:
- `LogClassificationCoverageTests` (Tools.Tests) scans all 104 stack
assemblies and fails if an address, identity or path parameter of a
`[LoggerMessage]` method is unclassified.
- `DataClassificationLoggingTests` (Core.Tests) pins erasing and HMAC
redaction, pass-through, the plain-factory drop, and both stack
defaults.

Local results on net10.0:

| Suite | Passed | Failed |
|---|---|---|
| Core.Tests | 6,489 | 0 |
| Client.Tests | 3,231 | 0 |
| Tools.Tests | 7,210 | 0 |
| PubSub.Tests | 1,536 | 0 |
| Core.Diagnostics.Tests | 571 | 0 |
| Configuration.Tests | 241 | 0 |

- **Server.Tests:** the full run predates the final test fixes and had
12 failures. All 12 were in the fixtures fixed afterwards, which now
pass: RoleStateBinding (45/45) and the FileDirectory and
compatibility-logging fixtures (74/74 in total). The full Core.Tests and
Server.Tests runs were not repeated after those fixes.
- **Builds:** `UA.slnx` builds with 0 errors for net10.0 and net48, and
the whitespace check passes.
- **NativeAOT:** not confirmed locally, because the machine has no C++
linker. A trimmed publish of `Opc.Ua.Aot.Tests` reported no trim or AOT
warnings from the stack, the new packages or the generated logging code;
its only errors come from the TUnit test framework (`FSharp.Core` not
found). The `aot-test` CI job has to confirm the native build.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
2026-10-21 09:47:07 +00:00
5899354441 Clarify documentation, migration guidance, and SDK selection (#4823)
# Description

Remove stock phrasing and correct misleading or inconsistent technical
documentation. The initial two passes covered 367 Markdown files,
followed by an interactive review of 169 editorial flags. The U5
interpretation originally deferred in that review has since received the
upstream Mantis answer recorded by #4843; remaining server/spec
follow-ups stay explicit in the CTT log.

- Correct package responsibilities, migration steps, API names,
ownership, protocol descriptions and links. Keep bundled migration
guides synchronized. Preserve historical conclusions as historical
records with separate re-check notes.
- Provide at least two rendered TOC levels throughout `docs/`. Convert
appropriate architecture figures to Mermaid without changing code
examples, transcripts or historical quotations.
- Address the October 19 feedback with concrete setup/migration
instructions, corrected certificate paths and PubSub names, shorter
repeated caveats, consistent XML comments, and a full review of the
Concepts guide. Retain #4839's API corrections.
- Integrate #4841's runtime/performance guidance and review
incoming/recent prose. Preserve dated measurements rather than claiming
new benchmark runs; clarify allocation ratios, startup-only processor
settings, JIT warm-up and the benchmark commit's scope. Missing raw
measurement artifacts and repeat ranges remain tracked by #4842.
- Merge master through `e8c5d800a` in `32c6394b7`. Resolve the CTT log
conflict using the updated Mantis states and OPC answers, retaining
historical framing and re-checks for C28/C32/C43.
- Address the latest naming/index feedback in `adb5bf693`: rename the
guides to `docs/WotCon.md` and `docs/WotNodeSetConversion.md`; move the
provider and DataType-declaration contracts into the conversion guide
and remove `docs/WoT.md`. Rename `samples/DI` to `samples/Di` and
`samples/Isa95` to `samples/ISA95`. Add content-index READMEs to Di,
FluentApi, ISA95, Lds and PubSub; update solution entries, project
references, Docker/CI paths, test fixture paths and documentation links
with exact casing.

**WoT guide scope.** This branch retains the detailed conversion guide
that #4758 had replaced with an anchor stub. Conversion limits, verified
document sets, preservation rules and provider ownership now live
together in `WotNodeSetConversion.md`; binding/runtime guidance remains
in `WotBindings.md` and `WotCon.md`. The design-rationale/test-ledger
prose was shortened, the oversized argument-table cell moved into its
section, and existing anchors preserved.

**SDK policy is a separate, explicit user-directed change.**
`global.json` sets a stable minimum of `10.0.400`, `rollForward:
latestMajor`, and `allowPrerelease: false`. Newer major SDKs are
permitted; CI's explicit SDK pins are unchanged. README, GettingStarted,
DeveloperGuide and Recycling instructions reflect the possible analyzer
differences. The image-contract regression fix accepts compatible stable
SDK images instead of requiring exact equality with the floor.

The latest four rename/index threads are resolved after publication.
Three older threads remain open: universal obsolete-removal timing,
unconditional nonce-strength wording, and a blanket Vision NativeAOT
claim. Existing implementation/evidence constraints are explained in
their inline replies. The two-level TOCs remain as requested.

## Validation

- `git diff --check` and staged diff checks: passed. The untouched
upstream AutoID NodeSet retains its provenance bytes, including two
existing license-header trailing spaces.
- `.\.azurepipelines\validate-migration-plugin.ps1`: passed; 15 bundled
migration documents and manifests synchronized.
- Documentation/path validation: **101** documents retain two rendered
TOC levels (**2,228** entries), with idempotent formatting. **52** new
relative links and their anchors passed exact-case checks. No tracked
references to the old guide/sample paths remain. Both relocated
provider-contract sections are preserved in the destination.
- `dotnet sln UA.slnx list`: passed; shows the renamed Di/ISA95 projects
and the existing FluentApi/Lds/PubSub projects. Updated solution and
pump ProjectReference paths also passed exact-case existence checks.
- Latest focused commands, run from the repository root:

```powershell
# Passed: 61 channel send-order and reconnect tests.
dotnet test tests\Opc.Ua.Core.Tests\Opc.Ua.Core.Tests.csproj -p:CustomTestTarget=net10.0 --filter 'FullyQualifiedName~UaSCBinaryChannelSendOrderTests|FullyQualifiedName~TcpReconnectKeyAgreementTests' --verbosity minimal

# Passed: 5 pump datasheet tests; builds the renamed pump sample.
dotnet test tests\Opc.Ua.Di.Tests\Opc.Ua.Di.Tests.csproj -p:CustomTestTarget=net10.0 --filter 'FullyQualifiedName~PumpDatasheetConformanceTests' --verbosity minimal

# Passed: 4 repository NodeSet path/round-trip tests, including the moved pump models.
dotnet test tests\Opc.Ua.Types.Tests\Opc.Ua.Types.Tests.csproj -p:CustomTestTarget=net10.0 --filter 'FullyQualifiedName~RepositorySourceSurvivesSerializedLinkedPartitionsAsync' --verbosity minimal

# Passed: build, zero warnings/errors.
dotnet build samples\ISA95\MinimalIsa95Server\MinimalIsa95Server.csproj -p:CustomTestTarget=net10.0 -f net10.0 --verbosity minimal

# Passed: dependent sample-test project build, zero warnings/errors.
dotnet build tests\Opc.Ua.WotCon.Samples.Tests\Opc.Ua.WotCon.Samples.Tests.csproj -p:CustomTestTarget=net10.0 -f net10.0 --verbosity minimal
```

Earlier focused integration checks passed 2,620 tests across AutoID,
event queues/scheduling, source generation, MCP/package profiles and
WoT. Both reviewed Mermaid diagrams rendered locally without network
access. These checks are not a full solution run.

The full solution has **not** been rerun for this review. Earlier
`dotnet test UA.slnx -p:CustomTestTarget=net10.0 --verbosity quiet` did
not pass: three AOT projects rejected VSTest and 20 Vision tests failed
during graphics initialization with `OutOfMemoryException`. Remaining
long-running tests were stopped at the user's request. A focused Vision
rerun passed 558 tests and reproduced those 20 failures. The earlier
`dotnet test UA.slnx -p:CustomTestTarget=net48 --verbosity quiet` was
blocked by the former SDK pin and was not retried after the policy
change. The user approved proceeding with those limitations documented;
current remote CI is not claimed green.

## Related Issues

- Follow-up #4842 tracks the requested 2.0.2+ benchmark/tuning and
diagnostic-tool documentation refresh. #4841 supplies documentation and
earlier results, not all newly requested measurements.
- #4758 explains the conversion guide's earlier consolidation; the
current provider contracts are now consolidated into
`WotNodeSetConversion.md`.
- #4839 supplies the reviewed API corrections; #4843 supplies the
updated CTT issue record.
- The original prose cleanup and SDK policy were user-requested without
a source issue. No new runtime architecture is introduced by these
documentation edits.

## 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.
- [ ] 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.

---------

Co-authored-by: Roman Ettlinger <romanett98@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 10:10:55 +02:00
Klaus LandsdorfandMarc Schier 0bd7aaaad6 AutoID: keep every reader reachable from DeviceSet, and let a reader be a component of a machine (#4853)
# Description

Follow-up to the AutoID companion (#4825). Two changes to where the
AutoID server places its readers:

1. **`IAutoIdReaderBuilder.WithParent` is made conformant with OPC
10000-100 4.9.** Until now it placed a reader below another node
*instead of* `DeviceSet`, and the docs suggested "a component of a
machine" as the use case. A machine in the Machinery `Machines` folder
is not reachable from `DeviceSet`, so such a reader could not be located
from `DeviceSet`, which DI 4.9 requires for every `ComponentType`
instance (every AutoID device type derives from DI `DeviceType`). Now
the reader is still placed below the parent (`HasComponent`, unchanged),
and:
- when `DeviceSet` already reaches the parent through hierarchical
references (for example another reader, or any device it organizes),
nothing else is added: the reader is a component of a composed device
and DI 4.9 wants "only the root instance" referenced from `DeviceSet`;
- otherwise `DeviceSet` also organizes the reader (`Organizes`, both
directions), so it stays locatable.
2. **New opt-in `IAutoIdReaderBuilder.AsComponentOf(BaseObjectState
machine)`**, available only when AutoID is composed with Machinery
(`AddAutoIdToMachinery`, or a DI owner that loads the Machinery model).
It makes the reader an identifiable component of the machine per OPC
40001-1 11:
- the reader is placed below the machine's `Components` add-in
(`HasComponent`, as `MachineComponentsType` declares for `<Component>`);
- it gets the `2:Identification` add-in (`HasAddIn`) typed
`MachineryComponentIdentificationType`, which holds no copies: it
references the reader's own DI nameplate properties with `HasProperty`
(`Manufacturer`, `SerialNumber`, and those of `Model`,
`ManufacturerUri`, `ProductCode`, `HardwareRevision`,
`SoftwareRevision`, `DeviceRevision`, `DeviceClass`,
`ProductInstanceUri` that are set, plus `AssetId`/`ComponentName` when
published; `DeviceManual` and `RevisionCounter` are left out as 40001-1
10.2 recommends);
- the reader stays a device: the same `DeviceSet` rule as for
`WithParent` applies with `Components` as the parent, so in the usual
case (a machine in `Machines` that `DeviceSet` does not reach)
`DeviceSet` organizes it directly. It is not added to `Machines`.
- Checked before anything is registered, each failing with
`Bad_ConfigurationError` and a message naming the way out: the server
has no Machinery model (names `AddAutoIdToMachinery`), the machine is
not a node of the AutoID owner, the machine has no `Components` add-in
(names `WithComponents`), or the reader nameplate lacks `Manufacturer`
and/or `SerialNumber` (mandatory in `MachineryItemIdentificationType`).
`WithParent` and `AsComponentOf` exclude each other
(`InvalidOperationException`).

Default behaviour is unchanged: a reader built without
`WithParent`/`AsComponentOf` is organized by `DeviceSet` and not added
to `Machines`. Rollback on failure covers the new references: a failed
build removes the `DeviceSet` reference, the component and its add-in,
so the name can be used again.

## Specification basis

| Statement | Source |
| --- | --- |
| "The DeviceSet Object is the starting point to locate Devices. It
shall either directly or indirectly reference all instances of a subtype
of ComponentType with a Hierarchical Reference. For complex Devices that
are composed of various components that are also Devices, only the root
instance shall be referenced from the DeviceSet Object. The components
of such complex Devices shall be locatable by following Hierarchical
References from the root instance." | OPC 10000-100 (DI) 1.05.0, 4.9 |
| AutoID device types are "Subtype of DeviceType defined in OPC
10000-100". OPC 30010 does not mention `DeviceSet`, Machinery or
`Machines`. | OPC 30010 (AutoID) 1.01, 6.1 Table 7 |
| "Each Machine providing this building block provides the Components
Object, which directly references all identifiable components of a
Machine." / "Note: That does not preclude that a Machine is organizing
its components in various other hierarchies as well." | OPC 40001-1
(Machinery) 1.04.2, 11.1 |
| "The MachineComponentsType provides HasComponent References to all
identifiable components of a Machine"; Table 27: `0:HasComponent Object
<Component> 0:BaseObjectType OP`; Table 28: `<Component> 0:HasAddIn
Object 2:Identification MachineryItemIdentificationType M`; "As all
identifiable components shall contain the Identification Object". | OPC
40001-1 1.04.2, 11.2 |
| `MachineryItemIdentificationType`: `2:Manufacturer` M,
`2:SerialNumber` M, all other members O. | OPC 40001-1 1.04.2, 8.3 Table
16 |
| `MachineryComponentIdentificationType` is the component subtype of
`MachineryItemIdentificationType` (adds `2:DeviceRevision`); "It is not
recommended to use the optional Properties 2:RevisionCounter and
2:DeviceManual"; a missing component `2:ProductInstanceUri` "shall be
omitted". | OPC 40001-1 1.04.2, 10.2 |
| "it is recommended to keep this approach and let several References
lead to the same Node, rather than duplicating the Node" / Robotics
example: identification Properties "are only referenced from the
Identification functional group". | OPC 40001-1 1.04.2, 7.3 and Annex
B.2 (informative) |
| "It is expected that domain-specific companion specifications using
this specification will define what assets of the domain in what usage
are considered to be Machines or components of Machines." | OPC 40001-1
1.04.2, 11.1 (Note) |
| Organizes "can be used to span multiple hierarchies"; more than one
hierarchical path to a Node is allowed. | OPC 10000-3 1.05, 7.11, 7.5 |

**SDK decisions** (not specified by OPC 30010, which predates
Machinery):

- A reader is never added to `Machines`: no specification classifies an
AutoID device as a machine (40001-1 11.1 leaves that to the domain
specifications, AutoID is silent).
- A reader becomes a component of a machine only when the application
asks for it (`AsComponentOf`).
- `WithParent` keeps its parent and adds the `DeviceSet` reference
rather than rejecting parents outside `DeviceSet` (see below).
- The component `Identification` references the reader's nameplate
instead of copying it.
- AutoID does not add Machinery conformance units; the Machinery owner
advertises them for what its builders created.

### Why `WithParent` adds a reference instead of rejecting

DI 4.9 asks for reachability, not for a particular parent, and OPC
10000-3 allows several hierarchical paths to one node. Adding
`Organizes` from `DeviceSet` satisfies the "shall" while keeping the
parent the application asked for, and it is the same reference type
`DeviceSet` already uses for every reader (and the DI node manager for
every device). Rejecting such parents with `Bad_ConfigurationError`
would break existing callers that hang a reader below a machine or
folder. When `DeviceSet` already reaches the parent, no reference is
added, so the "only the root instance" rule of 4.9 holds for composed
devices. Reachability is decided once when the reader is built, by
following the forward hierarchical references of the owner's nodes.

### Why `AsComponentOf` keeps the reader referenced from `DeviceSet`

Machinery does not require machines to be in `DeviceSet` (the stock
builders put them in `Machines` only), so a reader that is only below
`Components` would again be unreachable from `DeviceSet`. The same
reachability rule as for `WithParent` applies: if the machine is itself
reachable from `DeviceSet` (for example a machine that is also a DI
device organized by `DeviceSet`), the reader is reached through it and
gets no direct reference, otherwise `DeviceSet` organizes it.

### Shape of `AsComponentOf`

It takes the machine's `BaseObjectState`, like `WithParent(NodeState)`
and `IMachineryBuildContext.BindMachine(TState, ...)`; with the stock
builders that is `IMachineHandle<TState>.State`. It requires the
machine's `Components` add-in to exist
(`IMachineBuilder.WithComponents`, or a companion-declared one bound
with `WithComponents(components, ...)`) instead of creating one, so the
Machinery builders stay the only place that creates Machinery building
blocks (`MachineryBuildingBlocks` organization, facets). The reader's
NodeState parent is the `Components` object, so the DI node manager
attaches it with `HasComponent` and the existing registration and
rollback code applies unchanged.

## Behaviour change for existing callers

- `WithParent(parent)` with a parent that `DeviceSet` does not reach
(for example a machine in `Machines`, or an application folder): the
reader is now *also* browsable from `DeviceSet` (one extra `Organizes`
reference, both directions), and an
`AutoIdClient.EnumerateReadersAsync()` (which browses `DeviceSet`) now
finds it. Its parent, NodeId and `HasComponent` reference are unchanged.
- `WithParent(parent)` with a parent `DeviceSet` reaches (for example
another reader): unchanged.
- Default placement: unchanged.
- `IAutoIdReaderBuilder` gains a member. The interface is implemented
only inside the package (instances come from `AutoIdRuntime.Reader`),
and the package is new in 2.0.

## Changes

- `src/Opc.Ua.AutoID.Server/AutoIdReaderPlacement.cs` (new, internal):
`DeviceSet` lookup, the reachability check, the `Organizes` reference
and its removal, `Components` resolution and validation, the nameplate
check, and the `Identification` add-in.
- `src/Opc.Ua.AutoID.Server/AutoIdRuntime.cs`: the builder's nameplate
delegate runs once (validated before anything is registered, then copied
into the DI builder); `AsComponentOf` resolves `Components` as the
parent before the device is created; after registration the add-in is
added and the `DeviceSet` reference kept; rollback removes the
reference.
- `src/Opc.Ua.AutoID.Server/Builders/IAutoIdReaderBuilder.cs`,
`AutoIdReaderBuilder.cs`: `AsComponentOf`, mutual exclusion with
`WithParent`, XML docs stating the exact behaviour.
- `docs/AutoID.md`: new "Placement" subsection under "Hosting readers"
(what DI 4.9 and Machinery 11 require, what is an SDK decision, a table
per builder call, an example), the composition table rows for readers
and `Machines`, the test fixture table.
`src/Opc.Ua.AutoID.Server/NugetREADME.md`: a short paragraph.
- Tests (below).

## Tests

- `AutoIdReaderLifecycleTests` (stand-alone owner):
`ReadersCanHangBelowAnExplicitParentAsync` now also asserts that
`DeviceSet` does not organize a reader below another reader;
`AReaderBelowAParentOutsideTheDeviceSetIsStillOrganizedByItAsync`;
`AFailedCreationBelowAnOutsideParentLeavesNoDeviceSetReferenceAsync`
(rollback); `AStandaloneReaderCannotBeAComponentOfAMachineAsync`
(`Bad_ConfigurationError` naming `AddAutoIdToMachinery`); argument
checks for `AsComponentOf(null)` and both call orders of
`WithParent`/`AsComponentOf`.
- `AutoIdCompositionTests` (Machinery owner, in process):
`AReaderBecomesAComponentOfAMachineAndStaysADeviceAsync` (parent is
`Components`, `HasComponent`, `DeviceSet` organizes it, `Machines` does
not, `Identification` with `HasAddIn` and the component type,
`HasProperty` to the reader's own `Manufacturer`/`SerialNumber`/`Model`
nodes, no `DeviceManual`, owned subtree);
`AComponentNeedsAMachineWithComponentsAndRegistersNothingOtherwiseAsync`;
`AComponentNeedsTheMandatoryIdentificationAsync`;
`AComponentNeedsAMachineOfTheOwnerAsync`;
`AFailedComponentCreationIsRolledBackAsync`.
- `AutoIdPlacementEndToEndTests` (new, hosted Machinery + AutoID over a
real session): a default reader, a reader below that reader, a reader
`AsComponentOf` a machine and a reader `WithParent` the machine.
Browsing `DeviceSet` finds the three roots and not the nested reader,
the outer reader reaches the nested one;
`MachineryClient.DiscoverMachinesAsync` returns only the machine and
`Machines` contains no reader;
`MachineryClient.EnumerateComponentsAsync` lists only the component
reader, `ReadIdentificationAsync` reads `Manufacturer`, `SerialNumber`,
`ProductCode` through the add-in, which is `HasAddIn`-referenced and
typed `MachineryComponentIdentificationType` and whose `HasProperty`
targets are the reader's own nameplate nodes;
`AutoIdClient.EnumerateReadersAsync` finds the three roots.
- The existing default assertions
(`TheReadersShareTheDeviceSetWithThePumpAndStayOutOfTheMachinesFolderAsync`,
`TheMachineryAndPumpsClientsStillFindTheirInstancesAsync`,
`ComposedReadersLiveBelowTheSharedDeviceSetAsync`) are unchanged.
- Fail-without: with the runtime wiring reverted to master (builder
surface kept so the tests compile), 10 of the new tests fail: all
`AsComponentOf` cases, both `WithParent` cases outside `DeviceSet` and
the three session tests that look for the component or the extra
`DeviceSet` reference. Removing only the rollback of the `DeviceSet`
reference makes both rollback tests fail. `NoReaderBecomesAMachineAsync`
and the existing default tests pass with and without the change, as
intended.

Local results (macOS arm64, SDK 10.0.402, head `238ed9b69` on master
`9bfc780e1`; the same three commits are now based on master `ac24123dd`
as `c6d70909b`, no AutoID file changed in between):
`Opc.Ua.AutoID.Tests` net10.0 318/318 passed; `Opc.Ua.AutoID.Server` all
TFMs and the test project with `CustomTestTarget=net48` build with 0
warnings, 0 errors (net48 tests run on Windows CI; there is no mono host
here). `dotnet format whitespace/style/analyzers --verify-no-changes` on
the changed files: no new findings beyond the conventions of the
surrounding test files.

## Commits

1. `fix(autoid): keep every reader reachable from DeviceSet`
2. `feat(autoid): make a reader a component of a machine`
3. `docs(autoid): document where readers are placed`

The branch is based on OPCFoundation/master `ac24123dd` (#4852, the last
master commit with a green build). Fork CI was not repeated on this
head: earlier fork runs of this branch failed only in test projects
outside this PR (the master build was broken by the NUnit 5 migration
#4840 and fixed in #4852; master is currently broken again in
`Opc.Ua.Core.Tests` by #4849). The upstream checks of this PR test it
against the current master.

## Related Issues

- Follow-up to #4825 (tracking issue #4824).
- #4823 (open) edits prose in other paragraphs of `docs/AutoID.md`
(table of contents, the DI-owner paragraph, providers). This PR adds a
subsection and changes two rows of the composition table and one row of
the test table; no hunk overlaps. The table of contents is deliberately
not touched here because #4823 adds the subsection entries for "Hosting
readers"; the "Placement" entry can be added there or after both are
merged.

## Checklist

- [x] 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.
- [x] 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. (Local build of the changed projects: 0 warnings, 0
errors.)
- [ ] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed. (Locally
the AutoID test project on net10.0; net48 is built but runs only on
Windows CI.)
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings. (Pending the upstream checks; see the CI note
above.)
- [x] I have addressed **all** PR feedback received.

---------

Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
2026-10-21 05:24:38 +00:00
Klaus LandsdorfandMarc Schier a51f08ad6f AutoID client: read a device's status without browsing (#4854)
# Description

A follow-up to #4825. It adds
`AutoIdClient.RefreshDeviceAsync(AutoIdDeviceSnapshot,
CancellationToken)`, which reads the variables of a reader device that
change at runtime again through the NodeIds a snapshot already carries.
It makes one Read and no Browse.

## Why

`ReadDeviceAsync` browses the children of the device on every call. OPC
30010 makes most device variables and methods optional, so the browse is
how the client finds out what this device publishes.
`AutoIdReadOptions.StatusOnly` only narrows the Read. This is the point
the Copilot review raised on #4825
([discussion](https://github.com/OPCFoundation/UA-.NETStandard/pull/4825#discussion_r4308032841)).
1458af9e9 documented the behaviour, but a client that polls the status
of N readers still pays N Browse and N Read requests per cycle. One Read
per reader would do.

`AutoIdDeviceSnapshot` already carries what a poll needs:
`DeviceStatusId`, `LastScanDataId`, `LastScanTimestampId`,
`DeviceLocationId` and `Methods`, all taken from that browse.
`RefreshDeviceAsync` uses them.

## Change

- **`RefreshDeviceAsync(snapshot)`** reads `DeviceStatus` and, when the
device publishes them, `LastScanData`, `LastScanTimestamp` and
`DeviceLocation`. That is one Read of one to four variables and no
Browse. It returns a copy of the snapshot (`with`), and every other
member, including `Methods` and the NodeIds, is unchanged.
- **Stateless.** The client keeps no cache. The caller holds the
snapshot, as with the other companion clients
(`PumpsClient.ReadPumpAsync`, `ScalesClient.ReadSnapshotAsync`, and
Robotics' `RefreshAsync` on its handles). A poller calls
`ReadDeviceAsync` once and then passes the snapshot to
`RefreshDeviceAsync` on every cycle.
- **Stale snapshot.** The snapshot is not validated against the server.
If it no longer matches the device (the device or a variable was
deleted, or the server assigned other NodeIds after a restart), the
status of the Read surfaces:
- a bad status for `DeviceStatus` (for example `BadNodeIdUnknown`) is
thrown as `ServiceResultException` with that status;
- a bad status for `LastScanData` is returned in `LastScanStatus` with a
null value;
- a bad `LastScanTimestamp` or `DeviceLocation` leaves that member at
its default.

In each case the caller reads the device again with `ReadDeviceAsync`.
That also picks up optional variables and methods the device published
after the snapshot was taken.
- **Arguments.** A null snapshot throws `ArgumentNullException`. A
snapshot without a `DeviceStatus` NodeId (not obtained from
`ReadDeviceAsync`) throws `ArgumentException`. Neither one makes a
service call.
- **Docs.** Covered in the XML docs of `ReadDeviceAsync`,
`RefreshDeviceAsync`, `AutoIdReadOptions` and `AutoIdDeviceSnapshot`, in
`docs/AutoID.md` and in the client `NugetREADME.md`. The wording
maintainers gave `AutoIdReadOptions` in 1458af9e9 stays as it is, with
one added sentence that points to `RefreshDeviceAsync`. `docs/AutoID.md`
states the cost of each call:

| Call | Network cost |
| --- | --- |
| `ReadDeviceAsync(reader)` | One Browse of the device's children
(BrowseNext only if the server pages the result), one Read of every
published variable |
| `ReadDeviceAsync(reader, AutoIdReadOptions.StatusOnly)` | One Browse,
one Read of two variables (`DeviceStatus`, `DeviceName`) |
| `RefreshDeviceAsync(snapshot)` | One Read of up to four variables, no
Browse |

The change is additive. No existing member changes behaviour, and there
are no new dependencies. The MCP `autoid_read_device` tool still uses
`ReadDeviceAsync`, because a single MCP tool call has no snapshot to
refresh.

## Tests

`tests/Opc.Ua.AutoID.Tests`:

- `AutoIdRefreshDeviceTests` (new, 7 tests) uses a `MockBehavior.Strict`
`ISession` with `VerifyNoOtherCalls`, so any Browse, BrowseNext or
second Read fails the test:
- one Read of exactly the four snapshot NodeIds (Value attribute,
`maxAge` 0), with the values mapped and the other members (`Methods`,
`DeviceName`, NodeIds) unchanged;
- a device without the optional variables is refreshed with a
one-variable Read;
- a stale `DeviceStatus` NodeId throws with the Read's status
(`BadNodeIdUnknown`);
- a stale `LastScanData` surfaces `BadNodeIdUnknown` in
`LastScanStatus`, and a bad timestamp or location resets those members;
  - a result-count mismatch throws `BadUnexpectedError`;
  - null and invalid snapshots are rejected without a service call;
  - cancellation reaches the Read.
- `AutoIdReaderEndToEndTests` (2 new tests against the hosted AutoID
server over opc.tcp):
- `ARefreshedSnapshotCarriesTheCurrentStatusAsync`: a reader is read
once, then faulted on the server. The refreshed snapshot shows `Error`
and the methods from the original browse.
- `ARefreshOfAStaleSnapshotSurfacesTheStatusOfTheReadAsync`: snapshots
whose NodeIds the server does not know (as after a deletion).
`LastScanStatus` carries `BadNodeIdUnknown`, and an unknown
`DeviceStatus` NodeId throws `BadNodeIdUnknown`.
- **Fail-without (mutation checks):**
- With a Browse of the device's children added to `RefreshDeviceAsync`,
every strict-mock test that reaches the Read fails.
- With a bad `LastScanData` status masked to `BadNotFound` (the
`ReadDeviceAsync` convention for optional variables),
`AStaleOptionalVariableSurfacesItsStatusWithoutThrowingAsync` and the
end-to-end stale-snapshot test fail.
  - Without the method, the tests do not compile.

## Verification (local, macOS arm64, SDK 10.0.402 from `global.json`)

Head 2118c2439 on OPCFoundation/master ac24123dd (#4852), which includes
the NUnit 5 migration (#4840). The new tests follow the NUnit 5
conventions in `docs/DeveloperGuide.md`: `Assert.ThrowsAsync` and
`Assert.MultipleAsync` are awaited, and the enclosing test returns
`Task`.

- `Opc.Ua.AutoID.Tests` on net10.0: 315 passed, 0 failed (9 of them
new).
- `Opc.Ua.AutoID.Tests` builds with `-p:CustomTestTarget=net48` with 0
warnings and 0 errors. Running net48 needs Windows, so the fork CI
covers it.
- `dotnet format` whitespace, style and analyzers at `--severity info`,
verify-no-changes on the changed `.cs` files: no findings in the new
code.
The branch is based on OPCFoundation/master `ac24123dd` (#4852, the last
master commit with a green build). Fork CI was not repeated on this
head: earlier fork runs of this branch failed only in test projects
outside this PR (the master build was broken by the NUnit 5 migration
#4840 and fixed in #4852; master is currently broken again in
`Opc.Ua.Core.Tests` by #4849). The upstream checks of this PR test it
against the current master.

## Related Issues

- Follow-up to #4825 (tracking issue #4824). Resolves the polling-cost
point of the Copilot review on `AutoIdReadOptions` in #4825.
- #4823 (open) edits prose in other sections of `docs/AutoID.md`. There
is no textual overlap with the client section changed here.

## Checklist

- [x] 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.
- [x] 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. (Local build of the changed projects: 0 warnings, 0
errors.)
- [ ] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed. (Locally
the AutoID test project on net10.0; net48 is built but runs only on
Windows CI.)
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings. (Pending the upstream checks; see the CI note
above.)
- [x] I have addressed **all** PR feedback received.

---------

Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
2026-10-21 05:11:03 +00:00
Marc Schier 2836da7ec8 Fix sessionless ResultTransfer ownership bypass and other issues. (#4834)
# Description

Correct ownership, endpoint validation, policy propagation, subscription
delivery, file handling and generated-model behavior across the stack,
tools and samples. Add focused regression tests and update the
corresponding guides and sample documentation.

### Previously included corrections

- Require an active, matching session to generate or access transient
ResultTransfer file handles. Cover both sessionless generation and reads
against a handle owned by an active session, using live session owners
in the Machinery tests.
- Keep physical filesystem operations within their configured mount and
apply the same confinement checks throughout file operations.
- Validate LDS semaphore paths consistently during server registration
and registration-store maintenance.

### Individual issues addressed

| # | Component | Issue and correction |
|---|---|---|
| 1 | AI jobs and transfers | Jobs and transfers did not consistently
retain their creator's identity and session. Apply ownership checks to
descendants, file handles, subscriptions and execution, and recheck
deployment permissions before dispatch. |
| 2 | Password-management client | Password-bearing calls could use
signing-only channels or follow a replacement connection. Capture an
encrypted transport generation and keep dispatch bound to it; preserve
ordinary non-password edits. |
| 3 | GDS and certificate-push clients | Connections omitted hostname
verification. Validate the original configured DNS name or IP address
before session activation. |
| 4 | Certificate push | Private-key uploads could use signing-only
calls. Require an encrypted, generation-bound dispatch while preserving
certificate-only updates and restoring the caller's identity afterward.
|
| 5 | MQTT client | The .NET Framework custom-trust callback could
discard TLS hostname, certificate-availability and usage errors. Retain
those checks while supporting configured issuer trust. |
| 6 | DTLS handshake | Certificate trust was not tied to the configured
peer address. Match the certificate's DNS or IP identity to the intended
endpoint. |
| 7 | Replicated address spaces | Serialized nodes lost per-node and
descendant permission metadata. Preserve role permissions, user-role
permissions and access restrictions, including updates to existing
replica nodes. |
| 8 | MCP PubSub imports | Imports without groups skipped checks on the
effective broker destination. Check the actual endpoint before resolving
credentials or activating the connection. |
| 9 | Reference-server accounts | A disabled username account could
authenticate again with its previous roles. Reject disabled accounts
during fresh authentication. |
| 10 | Console reference client | The sample disabled hostname checks
when connecting to an otherwise trusted peer. Retain the original
requested host and validate it before activation. |
| 11 | Capture replay and export | Replay and raw export could accept
non-capture files or include key material. Require complete PCAP/PCAPNG
containers and exclude key sidecars and decryption-secret blocks. |
| 12 | PubSub adapter fallback | The fallback client configuration
automatically accepted peer certificates. Apply normal trust and
hostname validation before activation or reads. |
| 13 | Kafka transport | Explicit TLS enablement was checked but did not
reach both final Confluent configurations. Use the same effective
protocol for validation, producers and consumers. |
| 14 | PubSub actions | Configuration rebuilds could widen a
connection-local action handler's scope. Preserve its original
connection association and reject mismatched dispatch. |
| 15 | PubSub readers | Reader-level message-protection settings were
accepted but ignored. Apply the effective group and reader settings
before accepting and publishing received values. |
| 16 | PubSub key groups | Different `SecurityGroupId` values could
share one connection-wide key context. Reject mixed groups on a
connection and require separate connections for distinct groups. |
| 17 | MCP session registrations | Session-name replacement or reuse
could retain another registration's credential bindings. Bind access to
the registration, caller and authenticated endpoint, and preserve that
identity when borrowing sessions. |
| 18 | Viewer HTTP host | Requests reached the viewer's OPC UA session
without consistent Host, Origin and port checks. Validate the request
destination and browser origin before using the session, while retaining
supported local MCP clients. |
| 19 | Vision file operations | File operations did not consistently
apply protected-file checks, and remote handles lacked caller and
connection context. Apply the shared checks and use host-side opaque
handles bound to caller, target and generation. |
| 20 | DTLS record integrity | The integrity-only MAC used a truncated
sequence number. Authenticate the full reconstructed sequence number and
epoch so records remain distinct across rollover. |
| 21 | AMB client sample | Connection setup omitted hostname validation.
Preserve the originally requested host and verify the peer identity
before activation. |
| 22 | Robot sample writes | The R2 `SpeedOverride` callback could
target R1 state. Bind each callback to its own robot and apply that
robot's write policy. |
| 23 | AI result properties | Optional result children inherited
remotely writable defaults. Preserve their declaration restrictions and
keep server-produced results read-only. |
| 24 | WSS peer-trust updates | Existing channels did not consistently
respond to peer-trust changes. Revalidate complete chains for forward
and reverse WSS channels and close only those that no longer validate. |
| 25 | GDS revocation checks | Caller-provided certificates could cause
issuer retrieval from embedded URLs. Resolve validation from configured
local trust, issuers and CRLs without fetching caller-provided
locations. |
| 26 | GMS observations | Published correction observations retained
writable model defaults. Reject remote writes while preserving
publisher-side updates. |
| 27 | Production-dataset monitoring | Subscription admission and
delivery could use the reporter's context instead of each subscriber's
permissions. Evaluate the subscriber at admission and on every delivery,
including cancellation and disposal paths. |
| 28 | WoT MQTT destinations | Hostless MQTT forms could imply a
localhost destination before loopback checks. Reject them before
credential lookup or connection attempts and validate the effective
endpoint. |
| 29 | XRegistry opaque reads | Content-ID reads and notifications did
not consistently honor encrypted-read requirements or the currently
accepted transport. Recheck the bound endpoint and reject transport
downgrades before rebinding, including when protected values are already
queued. |
| 30 | PubSub capture dissection | Key loading did not consistently
check the configured capture-root boundary. Check each actual key path
immediately before opening it. |
| 31 | PubSub publication queues | Imported WriterGroup queues were not
fully checked against publish-topic grants. Check the destination
actually used by each group. |
| 32 | MCP PubSub mutations | A caller-supplied mode could select
subscribe grants for an operation that publishes. Derive the required
direction and parent context from the actual mutation. |
| 33 | Generated write masks | Model import and generation could widen
an explicit `UserWriteMask`. Preserve it through import, copy, merge,
inheritance and dependency metadata. |
| 34 | Generated method flags | An explicit `UserExecutable=false` could
be lost. Preserve its specified state and resulting method behavior
through the same model paths. |
| 35 | Generated role identifiers | Unresolved role identifiers could
cause permission entries to be omitted. Preserve namespace-qualified
role IDs and report a diagnostic when a policy cannot be represented. |
| 36 | Aggregation-server defaults | The `localhost` default did not
reliably constrain listener binding. Use numeric loopback for the
default endpoint and command-line fallback. |
| 37 | Certificate-validation cache | Cached results could skip current
leaf or issuer validity checks. Recheck validity windows on cache hits
and keep hostname validation independent of cached chain trust. |
| 38 | Software-update folders | Revision names and marker contents
containing dot segments could select paths outside the software root.
Normalize and check every derived path against that root. |
| 39 | USDA arrays | Array elements were not encoded for their string or
asset context. Escape them as data and preserve valid composition and
round-trip behavior. |
| 40 | Adapter hot reload | A changed dataset definition with the same
name could keep old fields, subscriptions or cached values active.
Compare complete immutable definitions and retire old state before
replacement. |
| 41 | Adapter index ranges | Subscription publication could ignore
`IndexRange` and return the complete value. Preserve the requested
projection and its status and timestamps; reject unsupported range
providers. |
| 42 | PubSub metadata and targets | Metadata from one connection could
change another connection's target mapping. Scope metadata to its
connection and provenance, and resolve targets using actual typed field
IDs rather than field names or unintended positional fallback. |
| 43 | Scales product locks | Product locks were not applied across
every remote mutation path. Apply them consistently to value writes,
filling calculations and related recipe/controller operations while
retaining permitted updates. |
| 44 | Event permission caching | Cached event decisions could outlive
identity, role or node-policy changes. Revalidate the current
subscriber, event type and source permissions instead of reusing a stale
decision. |
| 45 | WoT Modbus destinations | Hostless Modbus forms could imply
localhost before loopback checks. Reject them before connecting and
validate the effective endpoint. |
| 46 | MCP local PKI tools | Peer-trust mutations did not consistently
require the caller's `CertificateAdministration` grant. Check the
capability before trust and untrust operations. |
| 47 | Robot-control HTTP bridge | The bridge lacked consistent Host,
Origin and port validation and caller-specific session grants. Apply
those checks before borrowing the OPC UA session and retain supported
local clients. |
| 48 | Subscription transfers | Transferred subscriptions could retain
cached notifications after the destination's permissions changed.
Require authorization continuity between source and destination
identities and roles, invalidate stale retransmission data when
continuity fails, and preserve retransmission behavior for compatible
transfers. |

### Compatibility notes

- Replication uses the versioned NSRP v1 policy envelope. Upgrade peers
together and re-encode or reseed legacy topology from the authoritative
model; missing permission metadata is not inferred from old records.
- Integrity-only DTLS peers must be upgraded together for the full
sequence/epoch MAC. AEAD behavior is unchanged.
- Distinct PubSub key groups require separate connections.
- Custom sessions that cannot capture the actual encrypted transport,
and subscription providers that cannot honor a requested range, are
rejected rather than silently changing the requested behavior.
- Subscription transfers retain retransmission data only when the
destination authorization remains compatible with the source snapshot;
otherwise the old sequence numbers and cached data are invalidated.

## Related Issues

No separate tracking issue is linked. The individual corrections are
listed above.

## 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.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [ ] 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.
2026-10-21 05:37:29 +02:00
b72eb44a31 Redundancy: bound the Raft log with the engine's snapshots (#4836)
# Description

**Draft: builds on #4835 and waits for a RaftCs release** containing
marcschier/raft-cs#9, marcschier/raft-cs#10 and marcschier/raft-cs#11,
anticipated as 1.1.4. It contains the commit of #4835; review only the
second commit.

`RaftSharedKeyValueStore` can capture and restore its state (#4794), but
`IRaftConsensus` carries no log indices, so the store cannot bound the
RaftCs log, which grows without limit. This change uses the engine's
snapshots:

- `IRaftSnapshotConsensus` (`CommittedItems` with log indices and
snapshots, `CompactAsync`) and `RaftCommittedItem`.
`DefaultRaftConsensus` implements it over `RaftNode.CommittedItems`,
`CreateSnapshotAsync` and `CompactAsync`.
- `RaftSharedKeyValueStore` gains a constructor overload with a
compaction interval and an optional logger (the existing constructors
are unchanged) and `AppliedIndex`. Over an `IRaftSnapshotConsensus` it
applies the indexed stream. A snapshot on that stream (a lagging replica
installing the leader's snapshot, or the first item after a restart)
replaces the state and publishes the differences to watchers. Every
compaction interval it snapshots its state into the engine, which
discards the log behind it. A compaction that fails is logged and
retried at the next interval.
- The engine snapshot is a format marker followed by the snapshot
encoding of #4794, so it carries the command position: the commands
after it keep the positions they have on every replica, and
`CreateSnapshotAsync`/`RestoreSnapshotAsync` keep working over it. No
compaction runs while a store restored with `FromFirstCommand` still
skips the replayed commands its snapshot holds.
- A snapshot in another format stops the applier. Pending and later
proposals and watches then fail with its error instead of waiting.
- `AppliedIndex` is published once the state holds an item, before
watchers and the proposer learn of it.
- Registration: `RedundancyConsistencyOptions.RaftCompactionInterval`
(server), and overloads of `AddRaftClientSharedStore` and
`AddRedundantClientSharedStore` that take the interval (client). Zero,
the default, never compacts. The store's logger comes from the
registered `ITelemetryContext`, else `ILoggerFactory`.
- `HighAvailability.md` describes the compaction and warns that a
replica must not be rolled back to a release without engine snapshots
once the cluster compacted its log.

No public member is removed or changed.

## Related Issues

- #4794 (store snapshots), #4835 (RaftCs 1.1.4)
- marcschier/raft-cs#9, marcschier/raft-cs#10, marcschier/raft-cs#11

## Checklist

- [x] 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.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
(`RaftSharedKeyValueStoreCompactionTests`; registration tests in Server
and Client.)
- [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. (Redundancy
test projects on .NET 10 against a RaftCs build of the three pull
requests: Server 935, Client 135, Kubernetes 103, PubSub 45, Samples 17,
all pass; the Server and Client test projects build for net48.)
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings. (CI cannot restore RaftCs 1.1.4 until it is
released.)
- [x] I have addressed **all** PR feedback received.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
2026-10-20 14:48:20 +00:00
Marc 4115ef696a Merge origin/master and preserve data-channel lifecycle guards
Keep data-channel close notifications while adopting terminal-error transport draining. Retain recipient-scoped event visibility alongside source-emission admission and shared filter evaluation. Reconcile overlapping NUnit 5 assertion fixes.
2026-10-20 15:29:11 +02:00
Marc Schier 65a67dd2f4 WoT runtime materialization, binding execution and native source emission (#4757)
# Description

- WoT registry, prepared file store, immutable content, version leases,
dependency-graph sharing (advanced graph rewrite), coordinator,
materialization, views and atomic refresh.
- Explicit caller-context propagation (`IWotRegistryService` / versioned
registry / prepared host / publication capture) and the additive
`IXRegistryProjectionContextualDelete` capability.
- UA/HTTP/MQTT binding execution and compilation, plus the 1.02 FileType
bridge and discovery.
- WoT-specific native event, source-emission and readiness integration
(`MasterNodeManager.SourceEmissions`, `EventManager` identity admission,
external-value monitored sources, Fluent attach/detach) and event lease
conservation.
- All corresponding `WotCon.*` runtime tests, WotCon sample app code,
WoT docs, CI feature budgets and Samples.Tests, plus the WotCon Design
NodeSet/csv/csproj and ModelSourceParity tests.
- Joint model metadata: XRegistry model 0.7.0 / 2026-09-12
(NamespaceVersion 63563, NamespacePublicationDate 63564), RequiredModel
date 2025-01-08.
- XML-doc work from the frozen WIP, applied to G12-owned declarations
only.

## Related Issues

- Fixes #4751.
- Part of the WoT split tracked in #4511.

## Checklist

- [x] 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.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage. (Tests included, not yet run
against current master.)
- [ ] I have added all necessary documentation.
- [ ] I have verified that my changes do not introduce (new) build or
analyzer warnings. (Not run.)
- [ ] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed. (Not run.)
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.
2026-10-20 10:43:02 +00:00
Marc ad0190c001 Merge origin/master into data-channels-quic-experimental 2026-10-19 19:27:24 +02:00
Marc Schier 9bfc780e14 Update NuGet dependencies and migrate tests to NUnit 5 (#4840)
# Description

Update 55 NuGet dependencies to the latest stable releases available on
2026-10-19, preserving documented compatibility pins and
framework-specific version bands. Existing OpenUSD prereleases remain
unchanged because no newer releases are available.

Migrate existing tests to NUnit 5: await asynchronous assertions,
propagate async through helpers, handle nullable predicates, and replace
obsolete `Order` attributes with sequencing dependencies. Sequencing
dependencies use `AllowFailure = true`; filtering a dependent test also
runs prerequisites. Adjust fixture-level parallelism where NUnit
dependency scheduling requires it.

Keep Roslyn API/runtime pins intact; select newer Immutable/Metadata
runtime packages only for test and profiling hosts requiring them.
Update the approved SDK pin to 10.0.402 across local, CI, and container
builds. Document NUnit 5 conventions.
2026-10-19 15:23:29 +00:00
a10f9e252d Generic runtime continuation, batch lifecycle and readiness (#4759)
# Description

This pull request extracts and integrates generic server runtime and
continuation-management capabilities for OPC UA NodeManagers.

- **NodeManager lifecycle and routing:** add batch registration and
reload, staged publication and readiness notifications, captured routing
snapshots and reservations, and safe retirement. Wire the lifecycle
through host interfaces, async/sync adapters, `MasterNodeManager`, and
`StandardServer` startup and shutdown.
- **Browse and History continuations:** retain continuation ownership
and manager dependencies across requests and reloads; add atomic claims,
session caching, and cleanup when requests fail or generations retire.
Add historian resume state, tokens, dependency capabilities, and shared
continuation-store support.
- **MonitoredItem lifecycle:** support detaching and reattaching items
across node-manager generations, including event subscriptions shared
across root notifiers and correct defaults for detached-item
modification.
- **Runtime NodeSet references:** track reference additions and
deletions through supported API paths and preserve the tracked
references during reload.
- **Published views and materialization:** add immutable `TypeTable` and
`EncodeableFactory` snapshots with publication-owner lifetime
management, and provide a live node-materialization query for the
existing WoT projection-view consumer.
- **CI trace support and documentation:** add scripts and tests for
staging NUnit internal traces, and document NodeManager lifecycle,
session continuation ownership, and historical-access behavior.

## Related Issues

- Extracts the generic runtime portion of #4511.
- #4757 depends on this pull request; this pull request does not depend
on #4757.

## 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.
- [ ] 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.

---------

Co-authored-by: perf <perf@local>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-19 16:08:37 +02:00
0f85a9f719 AutoID: the OPC UA for AutoID Devices companion SDK (OPC 30010) (#4825)
# Description

This completes the AutoID contribution proposed in #4824: the model
package for [OPC 30010 OPC UA for AutoID
Devices](https://reference.opcfoundation.org/AutoID/v101/docs/) (1.01)
plus the server, the client, an MCP profile and a sample pair, in one
PR. It follows the newest companion pattern (GMS #4765, MachineTool,
CranesHoists, MachineVision).

## Commits

1. `fix(sourcegen)`: a structure property whose DataType a subtype
refines is redeclared with its own field (the NodeSet needs it;
unchanged from the first slice).
2. `feat(autoid)` model package `Opc.Ua.AutoID` (unchanged from the
first slice). Adds only `AutoIdReaderKind`.
3. `feat(autoid)` server package `Opc.Ua.AutoID.Server`.
4. `feat(autoid)` client package `Opc.Ua.AutoID.Client`.
5. `feat(autoid)` MCP profile `Opc.Ua.Mcp.AutoID`.
6. `feat(autoid)` samples `samples/AutoID`.
7. `build(autoid)` solution, package list and package validation tests.
8. `fix(autoid)` x3, `docs(autoid)` and `test(autoid)`: review fixes,
net48 compatibility, documentation, the native AOT test.
9. `fix(autoid)` x3 and `feat(autoid-client)`: `GetLocation` location
types, the `Client` of access events, the default scan cycle of the
simulated readers, and NodeIds and methods in the device snapshot
(details below).
10. `feat(autoid)`, `test(autoid)`, `docs(autoid)` (`afdd7858b`,
`22bc3b26e`, `ed25fe4ae`, on top of the master merge `1c7948f65`):
AutoID composes into an existing Device Integration owner like Pumps and
Scales (review feedback; see "Composition with other DI-based
companions").
11. `1458af9e9` (marcschier): the `AutoIdReadOptions` remarks state that
`StatusOnly` limits the values read and that each call still browses
(Copilot review).
12. `fix(autoid)` `3ef2f1507`: `GetDeviceLocation` answers with the same
status codes as `GetLocation`; `feat(autoid-mcp)` `f6405152f`:
`autoid_read_device` shows the new snapshot NodeIds and methods (details
below).

## Server: `Opc.Ua.AutoID.Server`

- Reader devices (RFID, OCR, optical, optical verifier, RTLS) on the DI
device model, created by a fluent builder (`AutoIdRuntime.Reader(name,
provider)`) and hosted with `AddAutoId` / `ConfigureAutoId` on
`IServiceCollection`.
- Hardware is behind provider interfaces: `IAutoIdReaderProvider`,
`IAutoIdScanProvider` (Scan), `IAutoIdAsyncScanProvider`
(ScanStart/ScanStop), `IAutoIdRfidProvider` (ReadTag, WriteTag,
WriteTagID, KillTag, LockTag, SetTagPassword, each opt-in by flag),
`IAutoIdRtlsProvider`. Providers push scans, presence, log entries and
access events through `IAutoIdReaderSink`. In-memory providers (an EPC
Gen2 tag model with banks, passwords, locks and kill) are included for
tests and samples.
- Optional children (methods, `LastScanData`, `DeviceLocation`,
`AntennaNames`, location methods) exist only when the provider supports
them. Facet profiles and conformance units are advertised only for what
is implemented (`IConformanceContributor`); the Full facet is withheld.
- `GetLocation` (OPC 30010 6.6.3.2) returns the location in the
requested `LocationType`. An unsupported type is `Bad_InvalidArgument`,
an unknown object `Bad_NotFound`, and a supported type the object's
location is not available in `Bad_InvalidState` (6.6.3.2: "not available
or not allowed e.g. due to special configuration"); a provider result of
another type never reaches the client. `InMemoryRtlsProvider` takes an
optional location converter, for example from the configured local
coordinate system (9.3.4) into WGS84.
- `GetDeviceLocation` (6.1.3.7) uses the same mapping: an undefined
location type is `Bad_InvalidArgument`, a device location that is not
available in the requested type `Bad_InvalidState` (it answered
`Bad_NotSupported` before). 6.1.3.7 lists only "Standard OPC UA status
codes defined for the Call Service in OPC 10000-4"; there
`Bad_NotSupported` means "The Method is not supported for the Object
instance", which is wrong for a published method, and OPC 10000-4
5.12.2.4 allows reusing an existing status code with the same canonical
meaning, so the method follows 6.6.3.2.
- Access events (7.10, 7.11): `Client` is "the name of the client
application which was the originator of the command", so it is the
application name of the session's client description (else its
application URI), not the user identity. The results of a `ScanStart`
scan name the client that started it; scans the device reports on its
own name the device.
- The simulated readers default to one scan cycle per second
(`CycleInterval`, configurable per provider; the length of a cycle is
device specific in OPC 30010), so an unbounded `ScanStart` raises at
most one scan event per second.
- Behaviour: one scan at a time with a generation counter;
ScanStart/ScanStop serialised; a scan started by a session stops when
that session closes; the maximum scan duration, tag read length and
result count are limited by `AutoIdServerOptions`; device status changes
and events are raised outside locks; registration is rolled back on
failure; readers, providers and the runtime are disposed in order and
in-flight operations are drained.

## Composition with other DI-based companions

A server has one Device Integration owner, and AutoID now uses the
existing harmonized composition instead of requiring its own node
manager:

- `AutoIdMachineryModelProvider` adds the AutoID model to the Machinery
owner's model-provider pipeline (dependency-ordered, `Order` 200) and
keeps the DI nodes the owner loaded, so DI and `DeviceSet` exist once.
- `AddAutoIdToMachinery` (after `AddMachinery`) registers it;
`ConfigureAutoIdFor<TNodeManager>` creates readers on any
`DiNodeManager` owner, successive delegates share one `AutoIdRuntime`;
`owner.CreateAutoIdRuntime()` is the path without DI hosting. The owner
disposes the runtime on shutdown.
- Readers are registered in the owner below its `DeviceSet`, NodeIds in
the owner's instance namespace (`AutoIdRuntime.InstanceNamespaceIndex`),
not in the `Machines` folder; the AutoID conformance units and facets
are published by the owner next to the DI and Machinery ones.
- `AddAutoId` remains for the "AutoID is the only DI-based companion"
case and is built from the same provider. Next to another DI owner it is
still rejected; the message names the composition API. (Bypassing that
check gives two `DeviceSet` copies; the server routes `DeviceSet` to the
first owner and readers in the second are not browsable, checked
locally.)
- `Opc.Ua.AutoID.Server` now references `Opc.Ua.Machinery.Server` (a
stable package), as Vision does; a stand-alone server still loads only
DI and AutoID.

## Client: `Opc.Ua.AutoID.Client`

Reader discovery (a failed browse propagates), typed calls for every
published method, a device snapshot with documented `AutoIdReadOptions`,
and an async event stream (`ObserveScan/Access/Presence/LogEntries`) on
`IStreamingSubscription`. Cancellation is honoured throughout.
`AddAutoIdClient` registers it for DI. Because most children of a device
are optional (6.1, 6.5.3, 6.6.3), the snapshot also carries the NodeIds
of `DeviceStatus`, `LastScanData`, `LastScanTimestamp` and
`DeviceLocation` and the browse names of the published methods
(`Methods`, `HasMethod`), taken from the same browse whatever the read
options, so a client can monitor the device and knows what it may call
without browsing again.

## MCP profile: `Opc.Ua.Mcp.AutoID`

`McpToolProfile.AutoId` with nine tools: list readers, read device,
scan, scan start/stop, observe scan events, read tag, get supported
location types, get location. `autoid_read_device` also returns
`deviceStatusId`, `lastScanDataId`, `lastScanTimestampId` and
`deviceLocationId` (NodeId strings, null when the device does not
publish the variable) and `methods` (the browse names of the published
methods), built explicitly without reflection. Registered in the host
builder, the profile switches of the other profiles,
`Test-McpGrade.ps1`, `McpREADME.md` and `.mcp/server.json`.

## Samples

`samples/AutoID/AutoIdServer` (a dock-door reader with a simulated
pallet) and `AutoIdClient` with a README and
`Test-AutoIdSamplePair.ps1`. The default port is not a common one; tests
use port 0.

## Other changes

- `Opc.Ua.Di.Server` gets `InternalsVisibleTo` for
`Opc.Ua.AutoID.Server` (`RegisterRootNotifierAsync`).
- `UA.slnx`, `.azurepipelines/expected-packages.txt`,
`PackageVersionPolicyTests`, `NuGetPackageSetValidationTests`.
- Docs: `docs/AutoID.md`, `docs/McpServer.md`, `docs/Profiles.md`,
`docs/Samples.md`, `docs/DeveloperGuide.md`, `docs/README.md`,
`docs/WhatsNewIn2.0.md`, `README.md`, NugetREADMEs. The "model package
only" statements are removed.
- All packages follow the root version (no preview override).

## Specification notes and questions for the maintainers

- The model is 1.01, published 2020-06-18 (the NodeSet header). OPC
30010 6.1.3.16 prescribes the `AutoIdModelVersion` string `"1.00"`,
which is what the NodeSet carries and what the server publishes.
- **Question:** the NodeSet's optical verifier `Scan` method returns
`WGS84Coordinate`, which looks like a model error. It is not published,
and providers that implement `IAutoIdScanProvider` for a verifier are
rejected. Is that the behaviour you want?
- The stack answers a call to an unpublished optional method with
`BadNotImplemented`.

## Design choices reviewers may want changed

- `Opc.Ua.Di.Server` gets `InternalsVisibleTo` for
`Opc.Ua.AutoID.Server`, so the server can call
`RegisterRootNotifierAsync`.
- `AutoIdReaderKind` (and `AutoIdReaderKinds.TypeIdOf`) lives in the
model package `Opc.Ua.AutoID`, not in the server package, so the client
and the MCP tools share it.
- The MCP profile is read-only/safe: no tools that write, kill or lock
tags.

## Tests

`tests/Opc.Ua.AutoID.Tests` has 306 tests (116 of the model slice, 190
new): reader lifecycle, scan semantics, tag access handlers, limits,
conformance, in-memory providers, hosting, and over-the-wire
client/server tests with result codes, events, concurrency and dispose.
`tests/Opc.Ua.Tools.Tests` has 13 AutoID MCP tests plus the updated
profile-count tests. New behaviour tests fail without the code (for
example a provider's own cancellation, a synchronous scan while a scan
stop is in progress; for the last round: `GetLocation` of a supported
type the object is not located in answered `Bad_NotFound`, access events
named the user "Anonymous" and attributed `ScanStart` results to the
device, and an unbounded `ScanStart` on a default simulated reader
reported 20 times in 400 ms). Composition: `AutoIdCompositionTests`
(stock Machinery owner, providers out of order, DI loaded once, instance
ownership, rollback, cancellation, runtime disposal, an
application-owned DI owner, a second DI owner rejected) and
`AutoIdMachineryCompositionEndToEndTests` (Machinery + Pumps + AutoID in
one hosted server, read back by the AutoID, Machinery and Pumps clients,
including a scan and the published profiles and conformance units).
Mutating the change (no lifetime registration, the DI instance namespace
for browse names, the old ownership message) fails three of them. The
sample pair script passes. `tests/Opc.Ua.Aot.Tests.Mcp` has an AutoID
profile test (`AutoIdMcpAotTests`); the project was published with
Native AOT on osx-arm64 and all 64 tests passed. Publishing it also
found two `JsonArray.Add<T>` uses in the MCP tools (IL2026/IL3050), now
fixed.

## Verification (local, macOS arm64, SDK pinned in `global.json`)

The branch contains the master merges `fc556be7e`, `1c7948f65` and
`af7feb886`; the three composition commits sit between the last two (the
latest merge touches no AutoID, DI or Machinery file). `1458af9e9`
(marcschier) and the two commits of the last round follow `af7feb886`.

- **Last round (`3ef2f1507`, `f6405152f`).** Builds with `-m:2
-nodeReuse:false -p:UseSharedCompilation=false`: `Opc.Ua.AutoID.Tests`
on all target frameworks and with `-p:CustomTestTarget=net48`,
`Opc.Ua.Mcp.AutoID` on net8.0/net9.0/net10.0, `Opc.Ua.Tools.Tests` on
net10.0: 0 warnings, 0 errors. `Opc.Ua.AutoID.Tests` 306/306 on net10.0;
`Opc.Ua.Tools.Tests` AutoID tests 60/60, and the profile, catalogue,
composition and package tests that reference AutoID (2241) pass. The
changed tests fail without the code: `GetDeviceLocation` answered
`Bad_NotSupported` for both cases, and `autoid_read_device` had no
`deviceStatusId`. `tests/Opc.Ua.Aot.Tests.Mcp` published with Native AOT
on osx-arm64 (no IL2026/IL3050 warning): 64/64 passed. `dotnet format`
whitespace, style and analyzers `--verify-no-changes` on the changed
files: no finding on a changed line (line endings aside, which the LF
checkout on macOS reports for every file).

- **Build (composition round).** `Opc.Ua.AutoID.Server` on all target
frameworks, `Opc.Ua.AutoID.Tests` on net10.0 and with
`-p:CustomTestTarget=net48`, and the AutoID server sample: 0 warnings, 0
errors. MCP tools and JSON unchanged, so the native AOT test was not
republished.
- **Build (earlier rounds).** The AutoID packages and
`Opc.Ua.AutoID.Tests` on net10.0 and, with `-p:CustomTestTarget=net48`,
on net48/net8.0/net9.0/net10.0; `Opc.Ua.Tools.Tests` and
`Opc.Ua.SourceGeneration.Core.Tests` on net10.0 (`-m:2 -nodeReuse:false
-p:UseSharedCompilation=false`): 0 warnings, 0 errors.
- **Tests.** `Opc.Ua.AutoID.Tests` 306/306 on net10.0 (composition
round; earlier round 288/288); `Opc.Ua.Tools.Tests` (AutoID, host
builder, profile and package tests) 2438/2438 on net10.0;
`Opc.Ua.SourceGeneration.Core.Tests` 4358 passed, 8 skipped, after the
master merge touched `NodeStateGenerator.cs`. net48 was built, not run
(no Mono on macOS); the fork CI runs it. The MCP tools and their JSON
are unchanged in the last round, so the native AOT test was not
republished.
- **Coverage.** Composition round: the local coverlet collector recorded
zero hits for every module in this environment (also for unchanged
code), so no new local number; the new paths are exercised by the 18
composition tests listed above and the fork CI uploads coverage. Earlier
round: cobertura from the AutoID tests: `Opc.Ua.AutoID.Server` 92.0 %
lines / 86.8 % branches, `Opc.Ua.AutoID.Client` 89.9 % / 80 %; 110 of
the 112 coverable changed lines of the last round are covered (the two
missed lines skip a browse result without NodeId or name). The generated
code of the model package is excluded as in the other model packages.
- **Format.** `dotnet format` whitespace, style and analyzers
(`--severity info`) `--verify-no-changes` on the changed files: clean.

Fork CI (full matrix including macOS): [run
39162077465](https://github.com/biancode/UA-.NETStandard/actions/runs/39162077465)
on `f6405152f`: green. (Previous round: run 39130154771 on `ed25fe4ae`,
90/90 jobs green; the upstream checks on the merge `af7feb886` green,
108 passed, 5 skipped.)
CodeQL: [run
39162079826](https://github.com/biancode/UA-.NETStandard/actions/runs/39162079826)
on `f6405152f`: green, no alert in a file this PR touches. (Previous
round: run 39130156598 on `ed25fe4ae`, green.)

## Related Issues

- Part of #4824 (tracking issue: AutoID companion SDK).

## Checklist

- [x] 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.
- [x] 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.
- [x] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed. (The
solution builds on all TFMs; the AutoID test projects ran on net10.0
locally, net48 runs in the fork CI.)
- [x] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [x] I have addressed **all** PR feedback received.

---------

Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-10-19 12:30:33 +02:00
Marc 99dca35158 Keep native validation packages out of standard publish 2026-10-19 08:47:02 +02:00
Marc 60ad14f786 Merge master into data-channels-quic-experimental 2026-10-19 08:46:58 +02:00
Marc 526f8765b9 Implement OPC UA Data Channels 2026-10-18 12:09:16 +02:00
fc0687a2dc Source generator: a NodeSet2 Variable without a Value has a null Value (#4798)
# Description

A Variable or VariableType in a NodeSet2 that has no Value element has a
null Value (OPC 10000-6 F.5); the runtime NodeSet2 importer loads it
that
way. The source generator initialized such a node with the default value
of its DataType instead ((int)0, (double)0, ...), so a server built from
the generated code reported values the model never declared.

The NodeSet2 importer now records that the node declares no Value
(NodeSetValueIsNull), the flag follows the node into the merged instance
hierarchy, and the node state generator emits Variant.Null for it when
no
value was merged in from elsewhere. ModelDesign inputs, where a missing
DefaultValue implies the DataType default, are unchanged.

The tests generate a DI-style NodeSet2 together with the DI NodeSet2 and
check the generated values of a type's InstanceDeclaration, of an
instance's children with and without a Value, and of a VariableType.

## Related Issues

None.

## Checklist

- [x] 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.
- [x] 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.
- [x] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
- [x] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [x] I have addressed **all** PR feedback received.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
2026-10-18 09:16:26 +02:00
8441351902 Client: let ManagedSession validate a rediscovered endpoint (#4791)
# Description

When a reconnect is rejected with a status that requires the endpoint to
be refreshed, ManagedSession reads the endpoint again from the server's
discovery endpoint and ConfiguredEndpoint.Update replaces the endpoint
in
use with whatever discovery returned. The application had no say: a
discovery answer that, for example, drops the security mode or presents
a
different server certificate was taken over as it was.

ManagedSession.ValidateRediscoveredEndpoint (a
RediscoveredEndpointValidator)
receives the endpoint in use and the rediscovered one before the session
is
recreated against it. A Bad result keeps the endpoint in use and fails
the
reconnect attempt with that result, so the reconnect policy decides what
happens next. Without a validator the behaviour is unchanged.

## Related Issues

None.

## Checklist

- [x] 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.
- [x] 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.
- [x] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
- [x] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [x] I have addressed **all** PR feedback received.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
2026-10-18 07:03:11 +02:00
Marc Schier 55bd944609 WoT: Types-side TD/TM-NodeSet conversion with consolidated corpus and minimal consumer compat (#4758)
# Description

Adds the pure Types-side WoT Thing Description / Thing Model to NodeSet
conversion: resolution, context, declarations, types, values,
localization, URI/format handling, bounded hashing, plus supporting
native XML codec, NodeSet alias/value helpers, RelativePathFormatter and
a JsonElement equality polyfill.

- Carrier-witness fix: a missing native event is accepted only when the
owning preserved type declares it through a `GeneratesEvent` reference
(directly or via its type/supertype chain) AND a bounded, validated
selected EventType carrier identity equals the normalized authored
native target. Native-present events get a full compare; an ambiguous
native key and a missing map stay strict; a readable document is never
archive authority.
- Support-only dependencies: additive per-instance, non-serialized
`WotDocument.IsSupportOnly(kind, name)` (ArgumentNullException guard).
The resolver marks support-only after closure; explicit selection wins;
the View builder excludes only true support-only members, so unit/actsOn
dependencies stay in the JSON. Nested controls cover inner value-only
projection under an outer value-only selection (unit kept, support-only)
versus an outer explicit SelectAll (explicit, not support-only).
- Documentation/XML comments and member-order only in commit 5c368d3a;
no logic change.
- Timeout repair on current head 6957dd46: invocation-scoped
`ArchiveDerivations` reuse removes repeated document-set derivation work
without a global cache (functional files
`WotNodeSetConverter.{Archive,ArchiveDerivations,Partitions,WoT}.cs`,
plus `WotArchiveDerivationsTests` and
`WotDocumentSetSharedValidationTests`). The original local full29
reproduction changed from 27 passed / 2 Refresh timeouts to 29 passed
with unchanged limits; official CI on this head FAILED at three
per-project time ceilings (see the evidence ledger). Temporary RF1
diagnostics were fully removed; the final tree differs from 0f7b9a09
only in the six functional files.
- Test-only net48 compile repair in commit b838907 (earlier head):
official CI on 5c368d3a was RED on the net48 build with CS1061 (see
ledger); the fix adds `using System.Threading;` and an unqualified
`CancellationTokenSource` in one test file. No production change.

Defaults: DI default document budget 256 versus the sample's explicit
1024. Corpus: canonical 540-JSON inventory; the actual manifest is 535
entries = 394 TD + 141 TM (the roles of the other 5 are not claimed).
Native Pumps assertions (9624 nodes / 339 partitions) are unchanged.
2026-10-17 20:43:25 +02:00
768b39e1be Server: surface failures to restore durable subscriptions (#4795)
# Description

SubscriptionManager.RestoreSubscriptionsAsync logged and swallowed every
restore failure: a subscription store that threw, a store that reported
an unsuccessful restore, and a stored subscription that could not be
recreated. The server then started without those subscriptions, the
store was told the restore had completed (so it cleaned up what it
held),
and nothing but a log line showed that subscriptions had been lost.

A failed restore now fails the restore, and with it the startup:
- a store that throws fails with BadInternalError and the store's
  exception as the inner exception;
- a store that reports Success = false fails with BadInternalError;
- stored subscriptions that cannot be recreated are collected and
  reported as an AggregateException, before the store is told that the
  restore completed, so the store keeps what it holds.

SubscriptionManager.ContinueOnRestoreFailure (default false) restores
the
previous behaviour for applications that prefer to start without the
subscriptions. Cancellation is no longer swallowed unless that option is
set.

The Quickstarts durable subscription store reported Success = false when
it had nothing stored; it now reports a successful restore of no
subscriptions, and keeps reporting a failure when its file cannot be
read.

## Related Issues

None.

## Checklist

- [x] 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.
- [x] 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.
- [x] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
- [x] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [x] I have addressed **all** PR feedback received.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
2026-10-17 20:40:45 +02:00
Marc SchierandCopilot Autofix powered by AI 7fb986bd0d Add recycling operator console and sample container images (#4810)
# Description

Add a local browser-based operator console for the synthetic recycling
line, using direct, encrypted OPC UA connections and the existing typed
coordination interface.

- Extract a sample-local, non-packable `Recycling.Client` library shared
by the server and console. Preserve the model XML bytes, namespace/type
identifiers, resource identity, wire format and journal schema; the
server retains its .NET 8/9/10 targets.
- Add a .NET 10 ASP.NET Core workstation host with bounded SSE
state/trend delivery, native companion inspection, paged
material/genealogy reads, and role-limited coordinator commands. Exact
command envelopes are retained for explicit retries; uncertain outcomes
are not automatically reissued.
- Add the operator interface using local HTML/SVG, CSS and JavaScript
modules: ten process groups, substep inspection, faults/constraints,
persistent quality holds, trends, material tracing and simulator
administration. Show raw quality/provenance and handle stale data, run
changes, browser expiry and large identifiers explicitly. No
browser-generated plant telemetry or frontend framework/npm build is
introduced.
- Keep application/user credentials in external certificate stores. Use
strict X.509 trust, signed/encrypted UA sessions, local browser
bootstrap sessions, same-origin/CSRF checks, and a conservative
configured control ceiling. This is a local synthetic sample, not a
physical emergency-stop or shared remote-user system.
- Register `recyclingserver` and `recyclingclient` in the existing
Images CI matrix, using source/prebuilt Dockerfile stages and the
existing portable-IL/multiarchitecture packaging flow. Add an optional
loopback-published Compose console service. No new schedules or NuGet
publication entries are added.
- Add managed, real-UA, Chromium, NativeAOT and image-contract
qualification, with Microsoft.Playwright as a test-only centrally
managed dependency, and update usage/deployment documentation.

## 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.

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-10-17 16:16:29 +02:00
Marc Schier 7e25e1a3d7 Extract remaining shared companion, rendering and packaging changes (#4806)
# Description

The shared AI deployment contracts and instance registration; DI
software-upload outcomes, digest handling and simulator; bounded
Positioning/Pumps reads; PubSub raw-data/matrix and security-provider
handling; Vision read-quality contracts; xRegistry resource writes and
instance projection; OpenUSD authoring, embedded viewport and
retained-frame capture APIs; standalone sample CLI controls; and the
generic Debug/RID package-policy and fork/Dependabot coverage-comment
fixes. The Linux native prerequisite step is retained for **Vision tests
only**.

## Related Issues

- Related tracking issue:
[#4461](https://github.com/OPCFoundation/UA-.NETStandard/issues/4461),
without automatic closure.
- Source work:
[#4462](https://github.com/OPCFoundation/UA-.NETStandard/pull/4462),
which remains intact for the coordinator's later integration.
- Immediate stacked predecessor:
[#4804](https://github.com/OPCFoundation/UA-.NETStandard/pull/4804).

## 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.
- [ ] 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.
2026-10-17 07:52:02 +02:00
4e77ba3f84 Redundancy: snapshot and restore for RaftSharedKeyValueStore (#4794)
# Description

RaftSharedKeyValueStore applies every committed command but offers no
way
to capture its state. An application that wants to bound the Raft log
cannot snapshot the store before compacting its consensus log, and after
a
restart it cannot restore the state without replaying the whole log.

Add CreateSnapshotAsync, which commits a barrier through the consensus
and
encodes the state in a versioned format, and RestoreSnapshot, which
replaces the state before the store starts applying the log. A malformed
snapshot raises FormatException; restoring after start raises
InvalidOperationException.

Tests: RaftSharedKeyValueStoreSnapshotTests (round trip, log applied
after
restore, restore after start rejected, corrupt snapshot rejected).

## Related Issues

None.

## Checklist

- [x] 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.
- [x] 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.
- [x] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
- [x] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
2026-10-16 20:10:13 +02:00
Marc Schier b1c0cd20fd Fix classic browse-path subscription restoration (#4814)
# Description

Persisted classic subscriptions retain their server/client handles but
previously lost relative-path resolution metadata. Transfer could
succeed, yet a later `ApplyChangesAsync` attempted to resolve an
already-created item and failed. The SDK now owns this restoration
lifecycle without exposing a public resolution-state setter.

- Preserve the originally monitored target in internal snapshot state
and restore both `ResolvedNodeId` and `Status.NodeId`.
Transfer/reactivation use handles without re-browsing; fresh creation
resolves paths again.
- Version classic subscription/configuration files and read existing
unversioned 2.0 files using frozen legacy wire layouts. Legacy missing
targets remain unknown rather than being guessed.
- Exclude existing items from creation-time resolution, reserve creation
before asynchronous translation, and reject stale translation results.
Failed deletion retains identity and pending retry state.
- Support forward-only snapshot input using position tracking and
bounded, per-ExtensionObject buffering in the binary decoder, including
short reads. No whole-file buffering or reflection-based serialization
is introduced.
- Add unit/integration regressions and document the supported lifecycle
and migration implications.

**Compatibility:** Newly written files require the updated SDK. Existing
2.0 files remain readable. Assigning a different `StartNodeId` or
`RelativePath` to a created/in-flight item now immediately throws
`BadInvalidState`; unchanged assignments remain allowed. No V2
subscription-engine changes or 1.5 XML import are included.

## 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.
2026-10-16 20:06:51 +02:00