# Description Reduces the server subscription API surface per the measurements from issue #4185. ## ISubscription: 42 → 30 members (#4185) A call-site census of the 11 "publish-pipeline protocol" members plus `Publish` (all 68 `src/` projects, samples, tools, fuzzing, tests) found **zero callers outside `Opc.Ua.Server`** and split the members three ways: - **Deleted (4):** `ItemReadyToPublish` and `ItemNotificationsAvailable` were dead — zero callers repo-wide and implementation bodies commented out since before 1.5.378 — the obsolete parameterless `SessionClosed()` had zero callers, and `TransferSessionAsync` (the one-shot direct transfer, which bypassed the reservation protecting a transfer against a concurrently closing source session) lost its last caller once the manager always uses the claim/prepare/commit protocol. - **Internalized (8):** `PublishTimerExpired`, `Acknowledge`, `PublishTimeout`, `SubscriptionTransferred`, `AvailableSequenceNumbersForRetransmission`, `QueueOverflowHandler`, `SessionClosed(ISession)` and `Publish` move to `internal interface ISubscriptionPublishPipeline : ISubscription`, implemented **explicitly** by `Subscription` (the `ISubscriptionMonitoredItemLifecycle` shape) so they are unreachable through the public concrete class too. `Publish` goes with them deliberately — it is the same hazard class (consumes notifications, advances sequence numbers) with the same single production caller. - **Kept public (1):** `ResendData` drops out of the issue's list — its only caller is the `Server_ResendData` UA-method handler in `DiagnosticsNodeManager`, resolving via `TryGetSubscription` like `GetMonitoredItems` does. It is service-facing, not pipeline. ## The pipeline contract is complete: SessionPublishQueue traffics only in it `SessionPublishQueue` becomes `internal sealed` (zero consumers outside the assembly; `SentMessageQueue` already was) and works **solely with `ISubscriptionPublishPipeline`** — no `ISubscription` and no concrete `Subscription` anywhere in the file. To get there, the internal transfer-claim protocol (`TryBeginTransfer`, `PrepareSessionTransferAsync`, `CompleteTransfer`, `AbortTransfer`, `TryRestoreSessionAfterFailedTransfer`) moved from `internal` methods on the concrete class onto the internal interface, implemented explicitly. That completes the contract, so `TransferSubscriptionsAsync` loses its two-tier shape: the ~6 `subscription as Subscription` null-guard branches and the degraded `TransferSessionAsync` fallback for non-`Subscription` implementations are gone — one code path, always the claim/prepare/commit/rollback protocol, and `ISubscription.TransferSessionAsync` is deleted with it. Subscription creation fails with `Bad_InternalError` when a `CreateSubscription` override returns a type not derived from `Subscription`, instead of publishing partially — such implementations were already second-class (transfer required the concrete type before this change). This is the headline behavior change for exotic extenders and is documented prominently in `docs/migrate/2.0.x/sessions-subscriptions.md`. The durable-subscription open question in the issue is answered **no**: `Subscription/Persistence/` never references `ISubscription` (the store deals in `IStoredSubscription` DTOs; restore constructs the concrete type via `Subscription.RestoreAsync` inside the manager), so the internalization cannot break a store implementer. ## #4186: measured, prototyped, and deliberately kept as-is Body inspection showed only 4 of the 10 `ISubscriptionManager` wrappers are pure lookup-and-delegate (`Republish`, `SetTriggering`, `ModifyMonitoredItemsAsync`, `SetMonitoringModeAsync` — one caller each); the other 6 carry logic with no `ISubscription` substitute. A collapse of the four was implemented and then reverted on this branch: it traded ten uniform id-addressed members for a mixed dispatch shape in `StandardServer` without reducing what a reader must understand. **Every subscription service keeps going through `ISubscriptionManager`**; the wrapper bodies now resolve against the pipeline-typed registry. The decision is recorded in `plans/ServerRuntimeAnalysis.md` (open question 5). ## Compatibility treatment Removals are outright, per the documented no-`[Obsolete]`-shim policy for these interfaces (`docs/MigrationGuide.md` — re-adding an interface member would break every implementer; net48/netstandard2.0 have no default interface methods). In its place: - Migration analyzer **UA0030** (pipeline members + `SessionPublishQueue`, distinguishing deleted no-ops from internalized protocol), with tests, following the UA0024 syntax-based pattern. The transfer-claim members were never public in 1.5.378, so they need no migration story. - `docs/MigrationGuide.md#ua0030` section with before/after code; the `docs/migrate/2.0.x/sessions-subscriptions.md` section that told implementers to *add* `SessionClosed(ISession)` is replaced with the derive-from-`Subscription` story; the 2.0.x README symptom table routes the rule. - All 11 removed/internalized members existed in 1.5.378 (verified against `master378`), so each is documented as a 2.0 break. ## Tests Converted, not deleted. `Mock<ISubscription>` setups of pipeline members become `Mock<ISubscriptionPublishPipeline>` — Moq proxies the internal interface via a new `InternalsVisibleTo("DynamicProxyGenAssembly2")` (keyless; the SDK appends `$(PublicKey)` under `SignAssembly`, and signed pipelines do not run tests, matching the existing `Opc.Ua.Redundancy.Kubernetes` precedent). A test-side extension shim forwards the original member names (pipeline + transfer protocol) so the direct call sites on concrete `Subscription` instances compile unchanged. The transfer-race tests' typed reflection probes into `m_abandonedSubscriptions` follow the field's new type. - `Opc.Ua.Server.Tests` (net10.0): 4795 tests, 0 failures - `Opc.Ua.Subscriptions.Tests`, `Opc.Ua.Subscriptions.Durable.Tests`, `Opc.Ua.Subscriptions.Classic.Tests` (net10.0): green - `Opc.Ua.MigrationAnalyzer.Tests`: green incl. the new UA0030 tests - net48 gate: solution build green; subscription-area fixtures green (the known pre-existing local hosting-test environment failures reproduce identically on unmodified master) ## Related Issues - Fixes #4185 - #4186 was measured and prototyped on this branch and deliberately **not** taken (see the section above); the decision is recorded in `plans/ServerRuntimeAnalysis.md`. ## 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. _(net10 suites fully green locally; net48 build green and subscription-area fixtures green — the remaining net48 hosting-test failures on my machine reproduce identically on unmodified master.)_ - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Migrating from 1.5.378 to 2.0.x
Version 2.0 introduces a major architectural change from pre-generated code files to runtime source generation and more efficient memory use, with several major breaking changes requiring changes to your applications.
This folder includes all of the 1.5.378 → 2.0 migration content as a set of focused sub-documents so you can read only the parts that affect your application. Start with the Migration sub-doc index below to find the sub-doc that matches the symptom you are seeing.
Automate the migration. Add the
OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzeranalyzer package to your projects to receive analyzer warnings and one-click fixes for many of the patterns in these sub-docs. Rule IDsUA0001–UA0020map directly to the type-safety patterns described intypes.md.
Pro TIP. Point your favorite coding agent at the
opcua-v20-migrationskill, which knows when to load each sub-doc and runs the codefixer for you.
Migration sub-doc index
Find the row that matches the error / API surface you are dealing with and read only the listed sub-doc. The agent skill uses the same table; loading a single sub-doc keeps the context window small.
| When you hit… | Read |
|---|---|
CS0029 / CS1503 / CS0266 on NodeId, Variant, DataValue, ExtensionObject, QualifiedName, LocalizedText, ArrayOf<T> / MatrixOf<T>, ByteString, StatusCode, XmlElement, EnumValue; [Obsolete] warnings on built-in type APIs (analyzer UA0001–UA0020) |
types.md |
Loggers (Utils.LogX, Utils.Trace), removed static logger helpers (Utils.SetLogger / Utils.SetLogLevel), telemetry context, constructor ITelemetryContext parameter changes, OLD-vs-NEW snippets, fluent DI registration (AddOpcUa().AddLogging().AddMetrics()), breaking-changes inventory, migration utilities |
telemetry.md |
OPCFoundation.NetStandard.Opc.Ua.* package upgrade, TFM changes, Newtonsoft removal |
packages.md |
Source-generated *Collection shims, NodeManager generator, default of boolean properties, project structure |
source-generation.md |
IEncodeableFactoryBuilder, IType, JSON / XML / binary encoders, EncodeableFactory.GlobalFactory, IJsonEncodeable, ComplexTypes namespace move |
encoders.md |
CustomNodeManager, NodeState clone / read / write helpers, OnAfterCreate(CancellationToken), FindChild / CreateChild NodeId assignment, INodeManager3, INodeCache.InvalidateNode, generics on BaseVariableState / BaseVariableTypeState, predefined-node processing, lock (node) on a NodeState, NodeBrowser.DataLock (analyzer UA0027) |
node-states.md |
IUserIdentityTokenHandler, IClientIdentityProvider, IUserTokenAuthenticator, IAccessTokenProvider, ITokenIssuer, IIdentityClaims, caller-supplied secrets, secret store |
identity.md |
CertificateValidator, ref-counted Certificate wrapper, CertificateManager, ICertificateProvider, obsoleted X509Certificate2 direct-exposure APIs, PushManagement transactions (ApplyChanges-gated TrustList updates) |
certificates.md |
ApplicationConfiguration changes, Data-Contract serializer removal, MinMetadataSamplingInterval → MinSupportedSamplingInterval, ParseExtension / UpdateExtension signature, session / browser state persistence |
configuration.md |
Session → ManagedSession, V2 subscription engine, GDS-client Task → ValueTask modernisation, removed obsolete GDS APIs, durable subscriptions, PubSub, reverse-connect, server ISubscription publish-pipeline members / SessionPublishQueue internalized (analyzer UA0030) |
sessions-subscriptions.md |
UaPubSubApplication.Create*, IUaPubSubConnection, UaPubSubConfigurator, IUaPublisher, AMQP transport, JsonEncodingMode.Reversible/NonReversible, PubSub JSON encoder changes, DataSetFieldContentMask RawData / timestamp behaviour |
pubsub.md |
AlarmConditionState state-transition behaviour, auto-emitted GeneralModelChangeEvent, ModelChangeAggregator, INodeCache.InvalidateNode triggered by model change |
alarms-model-change.md |
DateTime.UtcNow, Timer, deterministic time in tests; System.TimeProvider adoption |
timeprovider.md |
ITransportListener.Open / Close removed, ReverseConnectManager.StartService / Dispose obsolete, reverse-connect DI/provider migration, custom ITransportListenerFactory / ITransportListenerCertificateRotation implementers need the new async method names |
transport-listener-async.md |
All sub-documents
telemetry.md— Telemetry and Loggingpackages.md— Package, Target Framework, and Dependency Changessource-generation.md— Source Generationtypes.md— Improved Type Safetyencoders.md— Encoders and Complex Typesnode-states.md— Node States andINodeCacheidentity.md— Identity, Token Handlers, and Secretscertificates.md— Certificates andICertificateProviderconfiguration.md— Configuration and State Persistencesessions-subscriptions.md— Sessions, GDS Client, and Subscriptionspubsub.md— PubSub (Part 14): breaking API, transport, JSON, and field-encoding changesalarms-model-change.md— Alarms and Address-Space Model Changestimeprovider.md— Time and Timer Abstraction (TimeProvider)transport-listener-async.md— AsyncITransportListenerAPI (issue #3923)
See also
- Migration Guide landing page — index across
all versions, including the small
1.05.377→1.05.378and1.04→1.05legacy notes. - What's New in 2.0 — narrative tour of the 2.0 changes, grouped by theme and layer.
- Profiles — facet / profile coverage of the 2.0 release.