Initial commit. - #1
Closed
barnstee wants to merge 1 commit into
Closed
Conversation
AlinMoldovean
pushed a commit
that referenced
this pull request
Sep 17, 2017
Sync the latest version
AlinMoldovean
added a commit
that referenced
this pull request
Mar 8, 2018
Merge remote-tracking branch 'origin/v1.03' into v1.03
5 tasks
Archie-Miller
referenced
this pull request
in DreamsAndAdventures/UA-.NETStandard
Jul 22, 2024
5 tasks
5 tasks
|
Erich Barnstedt seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
marcschier
added a commit
that referenced
this pull request
Apr 21, 2026
… input - Rename Applications/ConsoleBoilerServer to MinimalBoilerServer. - Drop Design/BoilerDesign.xml; drive source generation from Model/Boiler.NodeSet2.xml exported via the reference client. - Source generator: extend the NodeSet2 input pipeline to honor [NodeManager] attribute bindings (was only wired on the design path). - Source generator: emit AddChild for real instance children of top-level (non-typed) parents unconditionally so folder hierarchies like Boilers/Boiler #1 materialize at runtime instead of only when the parent is treated as a type template. - Update AOT tests, docs, and solution to match the rename. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
marcschier
added a commit
that referenced
this pull request
Apr 21, 2026
Adds four new overloads to INodeManagerBuilder that locate nodes by
their TypeDefinitionId, so well-known singletons can be wired without
fragile browse paths:
builder.NodeFromTypeId(ObjectTypeIds.HistoryServerCapabilitiesType)
.OnRead(...);
For multi-instance types, an optional QualifiedName disambiguator
picks one match:
builder.NodeFromTypeId(ObjectTypeIds.BoilerType, 'Boiler #1')
.OnRead(...);
Resolution is backed by a new Func<NodeId, IReadOnlyList<NodeState>>
ctor parameter on NodeManagerBuilder; the source-generator template
emits a BFS walk over PredefinedNodes + children as __FindByType-
DefinitionId. Error matrix: BadNodeIdInvalid / BadNodeIdUnknown /
BadBrowseNameDuplicated / BadTypeMismatch.
- Tests: 8 new fluent-builder tests covering all resolution paths.
- MinimalBoilerServer: switches Boiler-lifecycle wiring to the
typeId lookup, exercising the path under NativeAOT.
- Docs: SourceGeneratedNodeManagers.md documents the four addressing
modes and the error matrix.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
marcschier
added a commit
that referenced
this pull request
May 4, 2026
Addresses PR #3730 review feedback from @AnatolyKochnev: when ProcessMessageAsync receives its first message (LastSequenceNumberProcessed == 0) and that message has SequenceNumber > 1, the gap-walk republish branch was skipped, so any retransmissible messages between #1 and #curSeqNum-1 were silently dropped. This mirrors the V1 fix in PR #3565 (which added placeholders for missing pre-first-message sequence numbers in the classic Subscription). Implementation: when prevSeqNum == 0, iterate AvailableInRetransmissionQueue (populated from the publish response's availableSequenceNumbers list) and call TryRepublishAsync for each entry whose unsigned-distance places it before curSeqNum. The cost is bounded by the server's retransmission queue size (typically a few hundred entries) instead of the raw seq-number gap, which can be arbitrarily large after a transfer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
marcschier
pushed a commit
that referenced
this pull request
May 12, 2026
ReferenceNodeManager.Dispose previously disposed m_semaphore BEFORE
m_simulationTimer. The simulation Timer fires on the threadpool, and
Timer.Dispose() does NOT wait for in-flight callbacks. So a callback
already scheduled by the threadpool could race past the semaphore
disposal and hit ObjectDisposedException inside DoSimulation:
SERVER ... [ReferenceNodeManager] Unexpected error doing simulation #1.
[ObjectDisposedException] Cannot access a disposed object.
Object name: 'System.Threading.SemaphoreSlim'.
--- at System.Threading.SemaphoreSlim.Release(...)
--- at ReferenceNodeManager.DoSimulation(...) line 5375
DoSimulation catches and logs the exception so it doesn't fault the
test, but the noise destabilises CI runs (observed on the
ubuntu-latest Conformance job at 16:36:55 UTC).
Fix: snap m_simulationTimer to a local, dispose it via the
Timer.Dispose(WaitHandle) overload, wait up to 2 s for the in-flight
callback to drain, then dispose the semaphore.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
marcschier
added a commit
that referenced
this pull request
May 27, 2026
Comments from @marcschier addressed: 1. **Remove mention of phases throughout** (Program.cs:59, ClientHelpers.md:4, all Docs/Opc.Ua.Di/*.md, MinimalPumpServer/README.md, ComplianceMatrix.md). Removed 'Phase 8X' / 'G1..G11' wording from comments, docs, table headers, and ComplianceMatrix.md sub-sections — kept the technical content intact. 2. **Add a separate software-update server sample** (PumpSoftwareUpdateSeeder.cs). Created Applications/MinimalSoftwareUpdateServer/ — a self-contained DI server that demonstrates the software-update facet end-to-end with a single 'UpdateableDevice #1' under DeviceSet and a seeded ISoftwarePackageStore. Removed the SoftwareUpdate seeding code from MinimalPumpServer/Program.cs and deleted MinimalPumpServer/SoftwareUpdate/ and its corresponding test. 3. **Implement model as library dependency spec/plan now** (Model/Opc.Ua.Di.NodeSet2.xml, PumpNodeManager.cs:113, SourceGeneratedNodeManagers.md:791/809). Removed the orphan Opc.Ua.Di.NodeSet2.xml from MinimalPumpServer (DI is already consumed as a project reference). Rewrote Docs/SourceGeneratedNodeManagers.md multi-model section to document source-generated library references as the **primary** mode and runtime XML loading as the fallback. Removed the 'cross-namespace XmlPrefix' workaround documentation. Added plans/SourceGeneratedCompanionSpecLibraries.md with the spec/plan for converting Machinery + Pumps to source-generated libraries (mirroring Opc.Ua.Di), including the two source- generator gaps that must be fixed first. 4. **Fix this / Needs still work** (PumpNodeManager.Configure.cs:46-51). Simplified the [NodeManager] explanation block — removed the G9 workaround narrative (G9 has been fixed); the class is hand-written for clarity, not as a workaround. 5. **Use With instead of Wire everywhere** (Configure.cs:92). Renamed WireIdentification / WireMeasurements / WireActuation / WireSignals / WireSupervision / WireMaintenance to With* throughout PumpNodeManager.Configure.cs. 6. **TryAdd instead of TryWire** (Configure.cs:133). Renamed TryWireVariable / TryWireVariableReadWrite / TryWireMeasurement / TryWireSupervisionEdge to TryAdd*. 7. **Need to use source generated code here** (PumpNodeManager.cs:113). Rewrote LoadPredefinedNodesAsync comment to describe the library-first model and point at the SourceGeneratedCompanionSpecLibraries.md plan instead of the global::DI.* workaround narrative. 8. **Needs fixing** (README.md:77). Rewrote the architecture section: removed the cross-namespace 'wrong namespace prefix' explanation, replaced with a forward-looking reference to the new plan doc. Updated method-name references in the feature table to match the With*/TryAdd* renames. 9. **Consider IAsyncEnumerable for all 3 api** (ClientHelpers.md:66). Changed DiTopologyClient.EnumerateDevicesAsync / EnumerateNetworksAsync / EnumerateChildrenAsync from ValueTask<IReadOnlyList<TopologyEntry>> to IAsyncEnumerable<TopologyEntry>. Production: switched to �sync IAsyncEnumerable + yield. Tests updated to use �wait foreach (via a CollectAsync helper). Verification (net10.0): - Full UA.slnx build: 0 errors, 0 warnings (CA1014 unchanged). - 163/163 Opc.Ua.Di.Tests pass. - 9/9 Opc.Ua.Pumps.Tests pass (3 SoftwareUpdateSeeder tests removed with the seeder). - 215/215 Opc.Ua.Server.Tests Fluent category pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
marcschier
added a commit
that referenced
this pull request
May 28, 2026
Materialises the OPC 10000-100 §10.3 SoftwareUpdateType address-space
surface under a DI device via a one-line fluent extension and wires
its method handlers to user callbacks (with library-supplied
'succeed immediately' defaults so the sample is runnable out of the
box).
Fluent surface (Libraries/Opc.Ua.Di.Server/Builders/):
device.WithSoftwareUpdate(packageStore, su => su
.UsePackageLoading() | .UseDirectLoading() | .UseCachedLoading()
.WithSoftwareFolder(folder)
.OnPrepare(...).OnInstall(...).OnConfirm(...).OnUninstall(...));
Address-space layout created under the device:
SoftwareUpdate (SoftwareUpdateType, OPC-DI i=1)
├─ Loading : Package|Direct|CachedLoadingType (default Package)
├─ PrepareForUpdate : Prepare / Abort / Resume methods
├─ Installation : InstallSoftwarePackage / InstallFiles /
│ Uninstall / Resume methods
├─ PowerCycle : (FiniteStateMachine, no method bindings)
└─ Confirmation : Confirm method
Wiring details (SoftwareUpdateFacetWiring.cs):
- Uses the source-generated CreateInstanceOf*Type extensions on
ISystemContext to create the typed state nodes.
- The factory only auto-populates a subset of the children
(CurrentState + Prepare/Abort/Confirm/Resume). The remaining
methods (InstallSoftwarePackage / InstallFiles / Uninstall /
PrepareForUpdate.Resume) are materialised explicitly via
CreateInstanceOf* method factories and added as HasComponent
children. Each new node gets a per-instance NodeId from the
manager's INodeIdFactory and has ModellingRuleId cleared.
- Method handlers use the async OnCallMethod2Async delegate so the
user callback runs without sync-over-async.
- Default install handler records the supplied SoftwareRevision
into the per-device ISoftwareFolder (auto-registered
MemorySoftwareFolder unless overridden via WithSoftwareFolder).
- All catch blocks translate exceptions into ServiceResult so a
buggy callback never crashes the server's method dispatcher.
Sample (Applications/MinimalSoftwareUpdateServer/Program.cs):
- Updated to call .WithSoftwareUpdate(store, su => su.UsePackageLoading())
so a client connecting to the sample now actually sees the SU
subtree on UpdateableDevice #1 instead of a bare DI device.
Tests (Tests/Opc.Ua.Di.Tests/DeviceBuilderSoftwareUpdateTests.cs):
- WithSoftwareUpdateAttachesSoftwareUpdateSubtree
- WithSoftwareUpdateMaterialisesAllStateMachineChildren
- UseDirectLoadingSwapsLoadingSubtype
- UseCachedLoadingSwapsLoadingSubtype
- OnPrepareCallbackFiresOnPrepareForUpdate
- DefaultOnInstallRecordsVersionInFolder
Docs (Docs/SoftwareUpdate.md):
- New end-to-end document covering the address-space layout, the
fluent builder surface, callback signatures, storage abstractions,
client side, the MinimalSoftwareUpdateServer walkthrough, and
spec references.
Validation:
- Opc.Ua.Di.Server net10.0/net9.0/net8.0/netstandard2.1/net48/net472:
0 Warning(s) 0 Error(s).
- UA.slnx Debug build: 0 Warning(s) 0 Error(s).
- Opc.Ua.Di.Tests (net10.0): 265/265 passed (the previous 259 plus
6 new SU tests).
Follow-ups deferred for a later iteration:
- File-transfer pipeline binding: GenerateFileForWrite +
CloseAndCommit currently delegate to the typed method handlers
without streaming the binary into ISoftwarePackageStore.AddAsync.
- Client helpers: SoftwareUpdateClient currently exposes only
ReadSoftwareVersionAsync; UploadPackageAsync / PrepareAsync /
InstallAsync / ConfirmAsync convenience wrappers will follow.
marcschier
added a commit
that referenced
this pull request
Jun 1, 2026
…eviceSet Bug: Pump #1 was referenced in PumpNodeManager.Configure.cs via builder.Node("Pumps/Pump #1/...") but never created anywhere. Every wiring call threw BadNodeIdUnknown which was silently swallowed by try/catch blocks, so the entire fluent simulation pipeline was dead code. Only Pump #2 (declared via ConfigureDevicesFor in Program.cs) actually existed, with no measurements / supervision / simulation. Fix: in PumpNodeManager.CreateAddressSpaceAsync, after the base call but before the fluent Configure step, materialise a typed PumpState (OPC 40223 PumpType) named "Pump #1" as a child of the DI DeviceSet. Uses the source-generated CreateInstanceOfPumpType factory + the manager's NodeIdFactory + AddPredefinedNodeAsync. Then strip the "Pumps/" prefix from every browse path in Configure.cs (Pumps/Pump #1/... -> Pump #1/...) so the fluent root resolver finds the freshly-created device by browse name in PredefinedNodes. Pump #2 stays as-is in Program.cs, demonstrating the parallel DI declarative path (CreateDeviceAsync + WithIdentification). README: update browse instruction to Objects > DeviceSet > Pump #1. Rewrite the "Add a second pump" section to document both patterns (hand-rolled CreateInstanceOfPumpType vs DI ConfigureDevicesFor). Verified locally: server starts on port 62599 and logs Materialised 'Pump #1' (PumpType) under DeviceSet, NodeId=ns=4;s=5001_Pump #1. followed by PumpNodeManager: address space ready (10140 predefined nodes). No skipping/path-not-found messages remain.
13 tasks
marcschier
added a commit
that referenced
this pull request
Jun 1, 2026
Resolves PR feedback threads 3333380238 / 3333396295 (DeviceHealth simulation): the pump sample exercised supervision flags (cavitation / motor-overheat) but never reflected them on the DI DeviceHealth variable that NAMUR NE 107 clients expect to observe. Important model nuance: PumpType inherits from TopologyElementType (not DeviceType), so PumpState does NOT carry DeviceHealth. Pump #1 stays unchanged. Instead the simulation drives the DeviceHealth variable on Pump #2 (a declarative DeviceState device created via ConfigureDevicesFor), reusing the source-generator AddDeviceHealth() helper. Manager-side (PumpDeviceIntegrationServer/PumpNodeManager.cs): * New 'public void RegisterSupervisedDeviceHealth( BaseDataVariableState<DeviceHealthEnumeration>?)' to attach an externally-materialised health variable to the simulation loop. * New supervised-health field on PumpNodeManager.Configure.cs (alongside the existing simulation state). * New 'public static DeviceHealthEnumeration MapSupervisionToDeviceHealth(bool cavitation, bool motorOverheat)' pure helper applying NAMUR NE 107 severity ordering: FAILURE > MAINTENANCE_REQUIRED > NORMAL. Motor overheat wins when both flags are active. * AdvanceSimulation now calls UpdateDeviceHealth() each tick which ClearChangeMasks-es the registered variable on transition so subscriptions receive the NORMAL ↔ MAINTENANCE_REQUIRED ↔ FAILURE flow. Program.cs: * After WithIdentification on Pump #2, calls pump.Device.AddDeviceHealth (source-gen helper), pump.WithDeviceHealth(NORMAL) to seed the value, and casts ctx.Manager to PumpNodeManager to register the variable with the simulation loop. Tests/Opc.Ua.Di.Tests/PumpDeviceHealthMappingTests.cs (new): * 4 unit tests on the pure mapping helper covering NORMAL, MAINTENANCE_REQUIRED (cavitation-only), FAILURE (motor-overheat- only), and FAILURE-wins (both flags) severity ordering. Validation: * dotnet build PumpDeviceIntegrationServer + Opc.Ua.Di.Tests (net10.0): 0 errors. * dotnet vstest TestCategory=Pumps|Hosting: 48/48 pass (was 44; the 4 new mapping tests join the suite). * Smoke test: server boots clean on opc.tcp://localhost:62612/PumpDeviceIntegrationServer with the RegisterSupervisedDeviceHealth wiring active and no warnings about missing DeviceHealth child.
marcschier
added a commit
that referenced
this pull request
Jun 1, 2026
Partial response to PR feedback threads 3333309679 / 3333300092 / 3333312536 / 3333325207 / 3333298144 (typed-node API + remove try/catch). The full typed-accessor generator (.Components() / .Properties() split per the user's preference) remains a follow-up; this commit delivers the building block that makes those typed accessors viable and addresses one of the two reasons callers had to wrap fluent wiring in try/catch (namespace mismatch on inherited type children). Problem: Browse paths like 'Pump #1/Operational/Measurements/FluidTemperature' fail strict resolution because PumpType inherits from MachineComponentType, so Operational/Measurements carry the Machinery namespace (ns=3) — not the Pumps namespace (ns=2) that the resolver uses for unqualified segments. Authors are forced to sprinkle 'ns=N;' prefixes on every cross-namespace segment or wrap each builder call in a try/catch to swallow BadNodeIdUnknown. Fix (Libraries/Opc.Ua.Server/Fluent/BrowsePathResolver.cs): * When the exact-namespace FindChild lookup fails on a non-root segment, fall back to a single GetChildren scan that matches by local name only. Returns the lone match. * If two or more children share the same local name in different namespaces, throw BadBrowseNameDuplicated with a message instructing the caller to disambiguate with an explicit ns=N; prefix. Bad ergonomics never silently picks the wrong child. * Root-segment lookup is intentionally NOT changed — the root resolver is namespace-aware and ambiguity at the root would silently match the wrong manager. Tests (Tests/Opc.Ua.Server.Tests/Fluent/BrowsePathResolverTests.cs): * ResolveFallsBackToLocalNameForCrossNamespaceChild — exercises the OPC 40223 pump pattern (root in ns=2, child in ns=3). * ResolveAmbiguousLocalNameThrowsBadBrowseNameDuplicated — covers the explicit-disambiguation contract. * ResolveExactNamespaceMatchSkipsFallback — confirms an explicit ns=N; prefix picks the right child without engaging the fallback (so ambiguous names don't surface a false-positive duplicate). Sample (Applications/PumpDeviceIntegrationServer/PumpNodeManager.Configure.cs): * Pump wiring keeps the existing try/catch blocks. The other reason callers needed them — optional children like Operational not being materialised by CreateInstanceOfPumpType — is a deeper source-generator gap (the factory only materialises mandatory children of the declared type) that the upcoming typed-accessor generator will fix via lazy AddXxx() helpers per child. Validation: * dotnet build PumpDeviceIntegrationServer + Opc.Ua.Server.Tests + Opc.Ua.Di.Tests (net10.0): 0 errors. * dotnet vstest BrowsePathResolverTests: 21/21 pass (was 17, with 4 new cross-namespace fixtures). * dotnet vstest TestCategory=Pumps|Hosting: 48/48 pass. * dotnet vstest TestCategory=Fluent|NodeManager: 245/245 pass. Follow-up (FB-3 phase 2): * Source-generator change emitting IComponentAccessor<T> / IPropertyAccessor<T> typed-child accessors with .Components() / .Properties() split (per user-confirmed design). * AddXxx() lazy-materialisation helpers on each generated *State class so optional children (PumpType.Operational, etc.) auto- instantiate on first builder access. * Sample rewrite to use the typed accessors + drop the remaining try/catch blocks.
marcschier
added a commit
that referenced
this pull request
Jun 2, 2026
Address 7 of the 16 unresolved review comments from round 3: - #6 Tools/Opc.Ua.SourceGeneration/ModelCompilation.cs:300 - add `using System.Collections.Generic` and `using Opc.Ua.SourceGeneration.Dependency` so the file no longer spells fully-qualified type names inline. Same sweep for the `System.Collections.Generic.`/`Opc.Ua.SourceGeneration.Dependency.` prefixes at other call sites in the file. - #7 Tools/Opc.Ua.SourceGeneration.Core/Generators.cs:634 (and 3 other sites in FluentBuilderGenerator + GeneratorOptions + ModelCompilationOptions) - drop `FB-3 phase 3` / phase / track references from comments and xmldoc; describe the feature, not the rollout. - #9 Libraries/Opc.Ua.Di.Client/Hosting/OpcUaClientDiBuilderExtensions.cs:69 - re-wrap the long `<item><description>...</description></item>` doc list to fit inside the 140-char editorconfig limit. - #12 Strip `global::` qualification from all hand-written PR code (Libraries/Opc.Ua.Di.*, Applications/PumpDeviceIntegrationServer). Only kept where required to disambiguate (test-side `Pumps.PumpNodeManager` / `Pumps.PumpNodeManagerFactory` collide with the source-generated `Opc.Ua.Pumps` namespace; restored `global::` on those refs). Hand-written code now matches the .editorconfig convention of letting `using` directives carry the namespace burden; source-generated code still uses `global::` (unchanged). - #15 Remove `// -----` / `// =====` ASCII-banner `#region`-style comments from SoftwareUpdateClient.StateMachine.cs, SoftwareUpdateClient.Upload.cs, SoftwareUpdateFacetWiring.cs and SoftwareUpdateFileTransferManager.cs. Inline section comments remain unchanged. - #16 Expand 344 one-line `/// <summary>text</summary>` declarations across 72 files in Applications/, Libraries/, Stack/, Tests/ and Tools/ into the three-line `/// <summary>\n/// text\n/// </summary>` form. - Carry-over from FB-3 phase 3: while sweeping `global::` also un-qualify the hand-written Pumps server callsites (Pump #1 wiring in PumpNodeManager.cs / PumpNodeManager.Configure.cs) so the new code matches the reviewer's convention. Validation: - `dotnet build UA.slnx` — clean (0 errors, 15 unrelated warnings reported transiently by the build engine). - `Opc.Ua.SourceGeneration.Core.Tests` — 3535 passed / 8 skipped. - `Opc.Ua.Di.Tests` (Pump + DI client + SoftwareUpdate filter) — 85/85 passed. The remaining 9 round-3 items (architecture relocation #8, `=>`→`{}` body sweep #10, ObjectType-proxy refactor #11/#14, brace-formatting sweep #13, pump server Configure refactor #1/#2, DeviceHealth + functional-group exposure #3/#5 plus DI doc disambiguation #4) are tracked separately and will land in subsequent commits.
marcschier
pushed a commit
that referenced
this pull request
Jun 2, 2026
Mirror the sentinel-keep-alive pattern from the stable sister test ProcessReceivedMessagesAsyncShouldProcessMessagesInOrderAsync so the worker is parked inside the keep-alive dispatch before any data messages are queued. Without the sentinel, a fast consumer could read the first arrival (e.g. #3) before the rest queued, leaving the priority channel nothing to re-order; later #1/#2 arrivals were then dropped as stale and the test would observe [2,3,4,5] instead of [1,2,3,4,5]. Also updated the XML remark to accurately describe the priority-channel guarantee (re-orders items present at the same read, not 'any burst regardless of consumer speed') and asserted on the sentinel + reordered tail separately.
marcschier
added a commit
that referenced
this pull request
Jun 2, 2026
…uilder + ad-hoc functional group Addresses 3 PR review threads from round 3: - FB-R3-1 (PumpNodeManager.cs:133) — split OnAddressSpaceReadyAsync into two clearly-named phases: an async ConfigureInstancesAsync(ct) that materialises predefined instances (currently Pump #1) and a synchronous fluent Configure(builder) pass that wires callbacks. CreatePumpInstanceAsync renamed to MaterialisePumpInstanceAsync to match the new phase boundary. The reviewer's suggestion to use the DiNodeManager.CreateDeviceAsync<TDevice> helper does not apply here because PumpType in OPC 40223 derives from MachineType (Machinery), not from the DI ComponentType hierarchy that CreateDeviceAsync's generic constraint requires — documented in the xmldoc on the new ConfigureInstancesAsync partial. - FB-R3-2 (PumpNodeManager.cs:150) — collapse the six-arg `new NodeManagerBuilder(SystemContext, this, nsIndex, lookup, lookup, lookup)` + `AttachToBuilder(builder)` + `Configure(builder)` + `builder.Seal()` quadruple to a single fluent chain. New `FluentNodeManagerBase.CreateFluentBuilder(ushort nsIndex)` helper constructs the builder, wires the three default PredefinedNodes resolvers, and attaches the event-source + simulation registries. New `NodeManagerBuilder.Configure(Action<INodeManagerBuilder>)` extension preserves chaining. Pump callsite now reads `this.CreateFluentBuilder(nsIndex).Configure(Configure).Seal();`. - FB-R3-5 (DeviceIntegration.md:230) — demonstrate the non-typed `WithFunctionalGroup(QualifiedName)` builder on Pump #2 with an ad-hoc `Diagnostics` functional group that surfaces LastError / ErrorCount / LastSelfTest as plain properties. Doc snippet appended to Docs/DeviceIntegration.md §'Functional groups'. New public surface in Opc.Ua.Server.Fluent: - FluentNodeManagerBase.CreateFluentBuilder(ushort nsIndex) - FluentNodeManagerBuilderExtensions.Configure(NodeManagerBuilder, Action<INodeManagerBuilder>) Validation: - Full build: 0 errors, 0 warnings (server + pump server + tests). - Tests/Opc.Ua.Di.Tests pump suite — 14/14 passing. - Tests/Opc.Ua.Server.Tests fluent suite — 247/247 passing.
marcschier
added a commit
that referenced
this pull request
Jun 5, 2026
- Move all channel-related types into `Stack/Opc.Ua.Core/Stack/Client/Channels/` subfolder (#13). - Un-obsolete `SessionReconnectHandler` and the SessionExtensions `ReconnectAsync(connection, ct)` / `(channel, ct)` extensions; remove all SRH-related `#pragma warning disable CS0618` suppressions from applications and tests. AttachChannel/DetachChannel remain obsolete (#1, #4). - Remove the shared-budget section from MigrationGuide.md (#3). - Mark `ManagedSession.AttachChannel`/`DetachChannel` `[Obsolete]` so the warning surfaces all the way up the supported API surface (#9). - Break the `<see cref=…>` line in `Session.ChannelManager.cs` to stay under 140 chars (#10). - Move `ChannelManagerSessionFactory` from `Opc.Ua.Gds.Client.Common` into `Opc.Ua.Client` as a public, documented session-factory option (#11). - Audit other client classes for `IClientChannelManager`-aware overloads; add `ISessionFactory`-accepting overloads on `GlobalDiscoveryServerClient`, `LocalDiscoveryServerClient`, `ServerPushConfigurationClient` so any session factory (including `ChannelManagerSessionFactory`) works (#12). - Refactor MCP server (`OpcUaSessionManager`, `Program`) to use `ManagedSession` + DI-resolved `IClientChannelManager`; remove `SessionReconnectHandler` and manual keep-alive reconnect (#2). - ConnectionStateMachine code-style fixes: use named delegate types, collapse multi-line callback properties to single lines, swap `timeProvider`/`maxTotalReconnectTime` constructor param order, fix multi-line declaration (#6, #7, #8). - HTTPS resilience: confirmed `Microsoft.Extensions.Http` and `Microsoft.Extensions.Http.Resilience` 10.6.0 support `net472`/`net48`/`netstandard2.1`; removed `#if NET8_0_OR_GREATER` gating from `ManagedSessionBuilder` and `OpcUaClientBuilderExtensions` and HTTPS csproj package refs (#5). Verification: - `Opc.Ua.Client` + `Opc.Ua.Core` + `Opc.Ua.Gds.Client.Common` build clean (0 warnings, 0 errors). - 56/56 core channel-manager / retry-budget / HTTPS factory tests pass. - 129/129 client (ManagedSession + legacy SRH back-compat + SessionExtensions) tests pass.
marcschier
added a commit
that referenced
this pull request
Jun 11, 2026
Phase 1 (security-agent MCP, svc 59eec07a-...) + Phase 2 (security- review SAST sub-agent on the PR diff) + Phase 3 (deep-dive) findings on branch `vsubs`. All CRITICAL/HIGH/MEDIUM findings fixed in-line; the MCP secrets-usage, code-integrity, policy-governance, and tls scans report no PR-relevant items. CRITICAL #1 — Unbounded partition fan-out under hostile server --------------------------------------------------------------- A hostile or buggy server returning `Bad_TooManyMonitoredItems` on every `CreateMonitoredItems` reply caused the wrapper to mint a fresh server-side `Subscription` per `TryAdd`, growing client memory and server-side handle space without bound. `MaxMonitoredItemsPerPartition = null` (the documented default) made the per-partition cap unlimited, so the reactive fallback was the only growth gate and it kept minting forever. Fix: introduce `SubscriptionOptions.MaxPartitionCount` (default `32`, comfortably above legit fan-out), wire it through `PartitionPlacementPolicy`, and stop minting at the cap. The composite collection returns the new `PlacementDecision.RejectMaxPartitionCountReached` state and `TryAdd` returns `false` — the caller observes the standard add-failed path. HIGH #2 — Snapshot consistency across LogicalGroupId not validated ------------------------------------------------------------------- `ManagedSessionExtensions.ValidateAndSortGroup` checked only `PartitionIndex` contiguity. Two snapshots crafted to share a `LogicalGroupId` but disagree on durability / publishing interval / lifetime would be merged into one logical wrapper with inconsistent partitions — letting a corrupted or attacker- controlled snapshot file smuggle non-durable behaviour into a durable subscription (or vice versa) at restore time. Fix: extend `ValidateAndSortGroup` to assert all snapshots in the group agree on every subscription-wide option (`PublishingIntervalMs`, `KeepAliveCount`, `LifetimeCount`, `MaxNotificationsPerPublish`, `MinLifetimeIntervalMs`, `Priority`, `PublishingEnabled`, `Disabled`, `SendInitialValuesOnTransfer`). Mismatches throw `BadDecodingError` with a descriptive message. MEDIUM #3 — Restore-path secondaries bypass forwarding handler --------------------------------------------------------------- `SubscriptionManager.AttachSecondaryPartitionAsync` (snapshot- restore fan-out path) passed the raw user handler directly to `MintPreloadedPartition` instead of the wrapper's `PartitionForwardingHandler`. This broke the documented single-threaded dispatch contract for restored multi-partition subscriptions (per `Docs/UnboundedSubscriptions.md`): the serialising semaphore was bypassed and the handler's `subscription` parameter pointed at the partition instead of the logical wrapper. Application handlers that mutate non-thread- safe state could silently race after restore. Fix: expose `LogicalSubscription.ForwardingHandler` as an internal accessor; `AttachSecondaryPartitionAsync` now uses the wrapper's existing forwarding handler when present (falling back to the raw user handler only when the wrapper was constructed in single-partition fast-path mode, i.e. `DisableUnboundedItemMode = true`). Validation (net10.0) -------------------- * `Opc.Ua.Client.csproj` builds clean (0 warnings, 0 errors). * `Opc.Ua.Client.Tests` V2 subscription unit tests: **43 / 43**. * `Opc.Ua.Subscriptions.Tests.UnboundedSubscriptionTests` integration tests: **3 / 3** (1 durable-only skipped). * `Opc.Ua.Subscriptions.Durable.Tests.*V2Async` integration tests: **6 / 6**. Findings dismissed ------------------ * Affinity dictionary growth (`PartitionPlacementPolicy`) — uses `StringComparer.Ordinal` so no CVE-2022-29117-class culture sensitivity; growth is application-controlled, not attacker-controlled. * Idle-timer race vs `DisposeAsync` — race window exists but worst observable outcome is an `ObjectDisposedException` swallowed by the disposer's `try/catch`. No attacker trigger and no integrity/confidentiality loss. * Cross-wrapper notification routing — each `Add` allocates its own `PartitionForwardingHandler`; `BindLogical` rejects re-binding; routing keys are partition server-id → partition's own handler; no structural path for partition of wrapper B to reach wrapper A's handler. * OPC UA spec compliance — `SetTriggering` enforces same- partition (Part 4 §5.13.6); durable hook ordering (Part 4 §5.13.9) is uniform across primary and secondaries.
marcschier
added a commit
that referenced
this pull request
Jun 11, 2026
…t doc (audit follow-up #2) Expanded the Certificates fuzz corpus from 9 seeds to 21 (+ 12) across all five categories. The security assessment doc flagged the corpus as "adequate but limited diversity" -- this commit closes the gap. Generator (Fuzzing/Opc.Ua.Certificates.Fuzz.Tools/Certificates.Testcases.cs) now produces, in addition to the original RSA application cert / CRL / CSR / PEM / extension seeds: X509Cert/ certificate-expired.der RSA cert with NotAfter in past certificate-selfsigned.der RSA cert with no separate issuer certificate-ecc.der P-256 ECC cert with ECC issuer X509CRL/ crl-multi-revoked.der CRL listing 3 revoked entries crl-empty.der CRL with no revoked entries X509CSR/ request-ecc.der ECC-signed PKCS#10 CSR request-multi-san.der CSR with 4 SAN entries PEM/ certificate-ecc.pem ECC PEM cert private-key-ecc.pem ECC PEM private key ASN1/ nested-sequence.der SEQUENCE containing inner SEQUENCE + OCTET STRING large-integer.der 256-bit BigInteger (past AsnReader fast paths) subject-alt-name-many.der SAN with 9 entries (5 DNS + 4 IP) Exercises code paths the original corpus missed: - ECDsa decode branches in cert + PEM + PKCS#10 parsers - CRL revoked-list loop with multiple entries + empty-list boundary - Validity-window logic for expired cert - Self-signed (no chain) decode path - Multi-entry SAN decode loop - AsnReader.ReadInteger multi-limb branch (past 32 / 64-bit fast paths) - Nested OCTET STRING containing further DER content A dedicated ECC issuer ("CN=Fuzzing Test ECC Root") is constructed because CertificateBuilder.CreateForECDsa() signs with the issuer's ECDsa private key, so an RSA-only issuer is rejected. Verified (net10.0): - Opc.Ua.Certificates.Fuzz.Tests: 460 passed (up from 220), 0 failed. The doubling reflects the 12 new seeds * the harness's Fuzz*Assets/FuzzGoodTestcases theory expansion across the 20 cert targets. - Opc.Ua.Encoders.Fuzz.Tests baseline: 4131/4131 passed. - Opc.Ua.Network.Fuzz.Tests baseline: 330/330 passed. - Combined: 4921 passed, 0 failed. DER seed files are committed via `git add -f` because the repo-wide `*.der` gitignore would otherwise exclude them (matches the original F2 commit's precedent of force-adding cert test seeds). Side effect: deleted plans/security-assessment.md. The doc was an untracked working-copy audit summary that has now had every item addressed: - 7.1 #1 (MUST FIX, parser bound): commit 7744146 - 7.2 #2 (corpus diversity): this commit - 7.2 #3 (IOORE whitelist): commit 80832f1 - 7.3 future work: explicitly out-of-scope, tracked in the PR description instead. Audit ref: section 7.2 #2 of the security assessment doc.
marcschier
added a commit
that referenced
this pull request
Jun 11, 2026
CI on PR #3869 was green for all functional tests after re-running the 4 flaky failures (aot-windows `BrowseAndReadComplexTypesAsync`, test-ubuntu-Subscriptions.Durable `DurableSubscriptionSurvivesSession CloseV2Async`, test-windows-History `NonExclusiveLimitAlarmActiveNormalCycleAsync`, test-windows-PubSub), all confirmed unrelated to branch changes (also fail on master CI). The only remaining gate was `codecov/patch` reporting 68.23% of diff hit against an 80% target (with 5pp tolerance ⇒ 75% effective threshold). Closes the gap by adding focused unit tests on the lowest-coverage new code paths from the security commit (#3869 `c6997689`): * `PartitionPlacementPolicyTests` (+5 tests): - `ConstructorTreatsZeroMaxPartitionCountAsUnbounded` - `ConstructorPreservesExplicitMaxPartitionCount` - `DecideRejectsOnceMaxPartitionCountReached` - `DecideAllowsExistingPartitionEvenAtMaxPartitionCount` - `DecideAllowsMintUntilMaxPartitionCount` Cover the CRITICAL #1 DoS guard end-to-end: the `maxPartitionCount` constructor parameter, the new `MaxPartitionCount` accessor, and the `RejectMaxPartitionCountReached` decision path. * `SnapshotGroupValidationTests` (NEW file, 14 tests): - `ValidateAndSortGroupSortsByPartitionIndex` - `ValidateAndSortGroupAcceptsSinglePartition` - `ValidateAndSortGroupRejectsDuplicatePartitionIndex` - `ValidateAndSortGroupRejectsPartitionIndexGap` - 8 individual mismatch checks (one per option: `PublishingIntervalMs`, `KeepAliveCount`, `LifetimeCount`, `MaxNotificationsPerPublish`, `MinLifetimeIntervalMs`, `Priority`, `PublishingEnabled`, `Disabled`, `SendInitialValuesOnTransfer`) - `ValidateAndSortGroupAcceptsAllOptionsMatching` Cover every branch of the HIGH #2 snapshot-trust guard. `ManagedSessionExtensions.ValidateAndSortGroup` promoted from `private static` to `internal static` so it is reachable from `Opc.Ua.Client.Tests` (existing `InternalsVisibleTo`). Validation (net10.0): * `Opc.Ua.Client` builds clean. * `Opc.Ua.Client.Tests` V2 subscription unit tests: **62 / 62** (previous 43 + 5 new placement policy + 14 new snapshot validation). Re-runs: * The 4 flaky tests above all PASS on re-run with no code change to them, confirming pre-existing CI flakiness (also observed on master in commits `4d613d11` and `a81d9817`).
marcschier
pushed a commit
that referenced
this pull request
Jun 12, 2026
…anager.DoSimulation DoSimulation can race with Dispose() on shutdown: the simulation timer fires and gets past the m_simulationEnabled guard, then Dispose() runs (which intentionally does NOT wait for in-flight timer callbacks - see the comment on Dispose, lines 79-87) and disposes m_semaphore while DoSimulation is about to call m_semaphore.Wait(). The resulting ObjectDisposedException is caught by the outer try/catch and logged at LogLevel.Error as "Unexpected error doing simulation", producing misleading noise in every CI test log that disposes a ReferenceNodeManager. The race itself is acknowledged and accepted in the Dispose() comment as a better trade-off than blocking Dispose on a Timer.Dispose(WaitHandle). The log entry is the only problem: it labels an expected, documented teardown race as an unexpected error. Filter ObjectDisposedException specifically, gated on `m_simulationTimer is null` so the suppression only kicks in once Dispose has actually run. A genuine premature semaphore-disposal bug elsewhere would still surface loudly via the generic catch. Observed in build 14598 (mac PR build for #3868), History.Tests fixture teardown for AlarmClientIntegrationTests: SERVER 04:54:05.298 [Quickstarts.ReferenceServer.ReferenceNodeManager] Unexpected error doing simulation #1. [ObjectDisposedException] Cannot access a disposed object. Object name: 'System.Threading.SemaphoreSlim'.
marcschier
added a commit
that referenced
this pull request
Jun 22, 2026
…, docs merge #4 (csproj): McpServer now needs only the Opc.Ua.PubSub.Diagnostics project reference; it transitively brings PubSub/Udp/Mqtt. Removed the 3 redundant direct refs. #3 (architecture): replace the in-transport capture taps with a DI-injected capturing decorator so the UDP/MQTT transports carry NO capture code (mirrors the UA-SC CapturingMessageSocket): - New CapturingPubSubTransport (wraps IPubSubTransport, taps send/receive to the observer, delegates the rest) + CapturingPubSubTransportFactory. - AddPubSubPcap() decorates every registered IPubSubTransportFactory; capture is injected only when DI-configured (call after AddUdpTransport/AddMqttTransport). - Reverted the S1 taps from UdpDatagramTransport/MqttBrokerTransport, their factories, and core AddPubSub; moved the capture seam types (IPubSubCaptureObserver/Registry, PubSubCaptureContext/Direction) into the Diagnostics lib (namespace Opc.Ua.PubSub.Pcap). Moved the seam test + added a decorator test. McpServer PubSubRuntimeManager updated to the reverted factory. #1, #2 (docs): merged Docs/PubSubDiagnostics.md into Docs/Diagnostics.md as section 5 (with the decorator architecture); the in-doc MCP-tools section now links to McpServer.md, which gained a PubSub tool catalogue. Deleted the standalone file; updated README link + UA.slnx. Builds: diagnostics lib net8/9/10 0/0, MCP net10 0/0, core PubSub net48 0/0. Tests: diagnostics 48 (incl. decorator), PubSub 1058, no regressions.
marcschier
added a commit
that referenced
this pull request
Jul 18, 2026
Implements the spec's pluggable per-format SchemaId fingerprint provider (Schema Registry SS6.6) and surfaces SchemaIdAlg (review findings #1/#4): - New ISchemaIdProvider + SchemaIdProviders static registry (Opc.Ua.Types) with the built-in Avro (CRC-64-AVRO), Arrow (SHA-256/ApacheArrow) and JSON (SHA-256/JCS) providers pre-registered; a host can Register additional providers for any format. - SchemaCache.ComputeSchemaId now delegates to the registry (identical fingerprint bytes for the built-ins, so the handshake/round-trip stay byte-compatible). - SchemaCacheEntry gains a computed SchemaIdAlg (derived from Format via the registry), additive - no constructor change. Verified net8.0 (also compiles netstandard2.0): PubSub.Tests 1379/0 (1 skipped), Build 0 warnings / 0 errors.
marcschier
added a commit
that referenced
this pull request
Jul 30, 2026
…4117) # Description A live OPC UA compliance audit of `samples/PumpDeviceIntegrationServer` against the **OPC 40223 Pumps** and **OPC 10000-100 (DI)** companion specifications found **13 address-space violations** plus a serialization bug in the MCP tool. Every fix lands at its **root cause in the shared stack** rather than as a workaround in the sample, so all servers benefit. The sample now exposes **N fully simulated pumps** (`--pumps N`) and advertises the DI conformance facets it actually satisfies. > This branch has been merged with `master` including **#4116** ("Add simulation to PumpDeviceIntegrationServer and Event modelling"), which independently reworked the same pump simulation and the same fluent alarm builders. See [Relationship to #4116](#relationship-to-4116) below for how the two were reconciled. ## Findings and fixes | # | Finding | Root cause | Fix | |---|---------|-----------|-----| | 1 | `OverTempAlarm` created by the fluent API did not exist in the address space | `AttachAlarm` attached the alarm with `AddChild` but never indexed it into the node manager | Register the node; the same gap existed in `AddObject`, `CreateInstance` and the state-machine creators | | 2 | Alarms were unsubscribable — `EventNotifier = 0` everywhere, no `HasNotifier` from `Server` | No event-source wiring at all | Set `HasCondition`, initialise `SourceNode`/`SourceName`/`ConditionName`/`InputNode`, promote `EventNotifier` up the ancestor chain, publish `HasNotifier` from the `Server` object | | 3 | Every instance NodeId was minted in the **DI standard namespace** (`ns=4;s=5001_Pump #1`) | `DiNodeManager.New` inherited the parent's namespace index | New `DiNodeManager.InstanceNamespaceIndex`; instances move to the server's application namespace | | 4 | `Server/Namespaces` described only 2 of 6 namespaces | The NodeSet2 importer drops nodes parented to namespace 0, so companion-spec metadata objects never reached the address space — and no route covered the server's own namespace | New `NamespaceMetadataPublisher` walks the `NamespaceArray` and fills version/publication date from `ModelDependencyAttribute` | | 5 | Vendor BrowseNames (`Diagnostics`, `LastError`, …) sat in the DI namespace | Builders defaulted to the parent's namespace | Default to the server namespace; **spec-defined BrowseNames are unchanged** | | 6 | `AccessLevel="5"` from the Pumps NodeSet was reported as `1` | The ModelDesign `AccessLevel` enum is not `[Flags]` and cannot represent `CurrentRead \| HistoryRead` | Carry the verbatim NodeSet2 bitmask and emit named `AccessLevels` constants | | 7 | `Historizing = true` while `HistoryRead` returned `BadHistoryOperationUnsupported` | Nothing reconciled the declared history surface with the wired historians | Startup clears the advertisement (including masking the attribute read callbacks) when no provider resolves; the sample wires a real historian | | 8 | Mandatory `ProductInstanceUri` unset | Sample omission | Populated on every pump | | 9 | Mandatory `TrueState`/`FalseState` empty on every supervision boolean | Absent in the official NodeSet; a server must still populate them | Populated on every pump | | 10 | Machinery `Machines` folder empty (OPC 40001-1 §9.2 says **shall**) | `TryAddToMachinesFolder` used `AddChild`, which *re-parents* the device away from `DeviceSet` | Add the `Organizes` reference instead | | 11 | BrowseName contained a space and `#` | Sample naming | `Pump_1` with the readable label in `DisplayName` | | 12 | `Maintenance` group materialised but empty | Sample omission | Populated | | 13 | Uninitialised values reported `Good` | No initial-value status | `BadWaitingForInitialData` until the first simulation tick | | — | `ServerProfileArray` advertised only `StandardUA2017` | `DiNodeManager` did not implement the existing `IConformanceContributor` | Contribute DI 1.05 server facets computed at runtime from what is actually wired, merged with `StandardUA2017` | | — | MCP tool serialised boolean `false` and numeric `0` as JSON `null` | `Variant.Null` is `default`, so `Variant.Equals` adopts the non-null type and compares against the default value | Dispatch on `BuiltInType` and use typed `TryGetValue` accessors instead of the prohibited `AsBoxedObject` | ## Relationship to #4116 #4116 landed on `master` while this branch was in flight and targeted overlapping ground. The merge is a **union of both intents**, not a fast-forward: - **`AlarmBuilderExtensions`** — kept both sides. #4116 contributes the parent-must-be-an-Object guard, `SetEnableState`, and the `HasEventSource` references; this branch contributes the `HasCondition` reference, condition source initialisation, **registration of the alarm with the node manager so it is browsable**, notifier promotion up the whole ancestor chain, and root-notifier registration. - **`SupervisionBuilderExtensions`** — took #4116's `SetAlarmActive`. It is more spec-correct than this branch's version: it honours `EnabledState`, resets Acked/Confirmed on activation, and computes `Retain` per OPC 10000-9 so an unacknowledged Condition stays retained after going inactive. This branch's null guard was kept so a malformed alarm cannot dereference a missing `EnabledState`. - **`PumpNodeManager.Configure.cs`** — took #4116's **push-based** simulation (`RegisterPumpSimulation`, `CreatePumpSimulation`, `IValueUpdater<T>`, `Initialize`/`Advance`/`Publish`, deterministic per-pump phase offsets) as the structure, then folded in this branch's compliance work: configurable pump count, Maintenance group, `TrueState`/`FalseState`, historian wiring, per-pump mandatory Identification values, and `BadWaitingForInitialData` until the first tick. - **README** — union, keeping #4116's address-space validation workflow section. Two test adjustments were needed, both because the merge legitimately changed behaviour the tests pinned: - `PumpHostedReferenceTests` now resolves pump NodeIds from the browse result instead of hard-coding the DI namespace, and waits for the first published value. `PumpCreatedAfterStartupJoinsTheLiveSimulationAsync` was **added** to restore #4116's coverage that a pump created after startup through `ConfigureDevicesFor` joins the live simulation. - `FluentAlarmRegistrationIntegrationTests` no longer asserts that `Retain` clears when an alarm goes inactive — that pinned this branch's weaker semantics, and #4116's Part 9 behaviour is correct. ## Breaking change Runtime-created DI device instance NodeIds move from the companion-spec namespace to the server's application namespace, e.g. `ns=<DI>;s=5001_Pump #1` becomes `ns=<application>;s=5001_Pump #1`. This invalidates NodeIds cached by clients from earlier 2.0 previews. It is **not** a model change — standard DI/Machinery/Pumps type NodeIds and spec-defined BrowseNames are untouched. Device integration hosting is new in 2.0, so there is nothing to migrate from 1.5.378 and this is deliberately *not* in `docs/MigrationGuide.md`. Anyone tracking 2.0 previews should rediscover devices by browsing `DeviceSet` rather than persisting NodeIds, and resolve namespace indexes from `NamespaceArray` per connection. ## Verification Compliance is pinned by regression tests that assert **through the server's service surface** (`BrowseAsync` / `ReadAsync` / `HistoryReadAsync`), not by inspecting `NodeState` objects in memory. That distinction mattered: three defects survived in-memory assertions and were only caught through the read path — most subtly, `Historizing` was cleared while `AccessLevel` still advertised `HistoryRead`, because attribute read callbacks re-applied the NodeSet bits. Post-merge on net10.0: | Suite | Result | |---|---| | `Opc.Ua.Di.Tests` | 326 passed | | `Opc.Ua.Server.Tests` | 3846 passed, 5 skipped | | `Opc.Ua.Tools.Tests` | 288 passed | | `Opc.Ua.History.Tests` | 506 passed, 27 skipped | | `Opc.Ua.SourceGeneration.Core.Tests` | 3722 passed, 8 skipped | The full solution was also built and tested on **net48** before the merge. Review feedback from `copilot-pull-request-reviewer` has been addressed in commit `badad31dc`: the shared `ServerObjectState` lock was removed (the forward `HasNotifier` edge is published through the owning node manager instead), MCP array serialization now preserves element types, and the pump option guards report the specific rejected option. The only failure is `ConfigureApplicationBuildsSharedClientAndServerConfigurationAsync`, a pre-existing certificate-store issue that **passes when run in isolation** and is unrelated to this change. ## Documentation Updated `docs/DeviceIntegration.md`, `docs/MigrationGuide.md`, `docs/HistoricalAccess.md`, `docs/DeveloperGuide.md`, `docs/McpServer.md`, `docs/README.md`, `samples/PumpDeviceIntegrationServer/README.md` and `tools/Opc.Ua.Mcp/README.md`, and consolidated the four node-manager documents into a single [`docs/NodeManagers.md`](docs/NodeManagers.md) — overview, built-in managers (including `MasterNodeManager`), core vs custom, registration, server address-space metadata, and source generation — with a TOC and all inbound links repointed. ## Related Issues No tracking issue exists for this work yet — it originated from an ad-hoc compliance audit of the pump sample. Given the size of the change and the runtime-breaking NodeId move, please open (or link) a tracking issue so the design is recorded as an ADR before merging. - Fixes # ## 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. - [x] 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 <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0d50c3fc-58b1-4c99-85a4-1e32c88666d9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.