# Description Every other companion specification in the stack ships as `<Family>`, `<Family>.Client` and `<Family>.Server` — `Opc.Ua.Di`, `Opc.Ua.ISA95`, `Opc.Ua.Positioning`, `Opc.Ua.Robotics`, `Opc.Ua.Vision`, `Opc.Ua.WotCon`, `Opc.Ua.XRegistry`, and `Opc.Ua.Lds.Server` without a family package. GDS was the one exception, carrying a `.Common` suffix that named nothing. | Before | After | | --- | --- | | `src/Opc.Ua.Gds.Common` | `src/Opc.Ua.Gds` | | `src/Opc.Ua.Gds.Client.Common` | `src/Opc.Ua.Gds.Client` | | `src/Opc.Ua.Gds.Server.Common` | `src/Opc.Ua.Gds.Server` | ## No source changes were needed `RootNamespace` was already the final name in all three projects — the namespaces have always been `Opc.Ua.Gds`, `Opc.Ua.Gds.Client` and `Opc.Ua.Gds.Server`, and only `AssemblyName` and `PackageId` disagreed. Nothing that compiles against these assemblies changes a `using` directive; the diff outside the moves is entirely ids, paths and prose. Directories were moved with `git mv`, and every one of the 73 file moves is detected as a rename at 100% similarity (bar the handful that also had a name in their text), so `git log --follow` and blame survive. ## Consumer impact The package ids change, which is visible to anyone consuming the NuGet packages: | 1.5.378 package | 2.0 package | | --- | --- | | `OPCFoundation.NetStandard.Opc.Ua.Gds.Common` | `OPCFoundation.NetStandard.Opc.Ua.Gds` | | `OPCFoundation.NetStandard.Opc.Ua.Gds.Client.Common` | `OPCFoundation.NetStandard.Opc.Ua.Gds.Client` | | `OPCFoundation.NetStandard.Opc.Ua.Gds.Server.Common` | `OPCFoundation.NetStandard.Opc.Ua.Gds.Server` | `docs/migrate/2.0.x/packages.md` gains a renamed-packages section — it is the document that undertakes to cover NuGet renames, and had no GDS entry — and the migration skill's package table gains the two rows a 1.5.378 consumer needs. That is the only hand-written content in this PR; everything else is mechanical. Assembly names follow the package ids, so the section also notes that binding redirects, trimming descriptors and signing manifests naming the files directly have to be updated. ## Build infrastructure Everything naming the projects or their output moves with them: - `UA.slnx` and `tools/MigrationAnalyzer.slnx` (both re-sorted — the project lists are alphabetical, and `Gds` now sorts before `Gds.Client`). - `.azurepipelines/expected-packages.txt` — 3 ids, also re-sorted for the same reason. - `.azurepipelines/signlistDebug.txt` and `signlistRelease.txt` — 18 paths each. Their internal ordering is left alone: that group was already unsorted, and reordering a signing manifest is diff noise. - `samples/Reference/ConsoleReferenceServer/Dockerfile`, the two metapackage nuspecs, the analyzer nuspec's package dependencies, and 8 `.csproj` references across tests, samples and tools. `preview-pack.slnx` is generated from `UA.slnx` by `generate-slnx.ps1`, so there is nothing static to edit. Nothing under `.github/workflows`, and no `.ps1`/`.yml` in `.azurepipelines`, names these projects. The migration analyzer's shim tree mirrors the source project layout by its own documented convention, so `Gds.Client.Common/` becomes `Gds.Client/` and its readme table follows. ## Deliberately not changed Prose naming past work: `plans/23` still records that its deferral happened during the "GDS Client.Common modernization", which is the name that effort had. File *paths* in the same document were updated. ## Validation `net10.0`: - `UA.slnx`: 0 errors. 14 warnings, all pre-existing CA1861/CA1850/CA1307 in `XRegistry`, `Types.Tests` and `WotCon.Tests` — none in anything this PR touches. - Assemblies build as `Opc.Ua.Gds.dll`, `Opc.Ua.Gds.Client.dll`, `Opc.Ua.Gds.Server.dll`, with no `*.Common.dll` produced anywhere. - `Opc.Ua.Gds.Tests`: 1,116 passed, 0 failed, 54 skipped. - `Opc.Ua.MigrationAnalyzer.Core.Tests`: 10 passed, 3 skipped. - A repo-wide grep for the old names returns only the intentional old → new mappings in the migration docs. ## Checklist - [x] Existing tests pass - [x] No new tests — this is a rename with no behaviour change - [x] Consumer-visible package rename documented in the migration guide 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
OPC 10000-21 Onboarding Demo
This sample demonstrates the registrar-administration part of OPC 10000-21 end to end over a real OPC UA connection. It uses only the model and runtime APIs shipped by this repository:
OnboardingRegistrarloads the generatedOpc.Ua.Onboardingmodel, exposes the standardDeviceRegistrar_Administrationobject, and bindsRegisterTickets/UnregisterTicketsto an injectedMemoryTicketStore.OnboardingClientconnects with a managed session, resolves the well-known registrar NodeId, and uses the DI-providedOpc.Ua.Gds.Client.OnboardingClient.run-onboarding-demo.ps1builds both applications, waits until the OPC UA server reaches its running state, proves an anonymous caller is denied, runs the authenticated client, and stops the exact registrar process in afinallyblock.
The demo covers ticket administration. It does not implement the
device-facing ProvideIdentities flow or a complete production GDS.
Run the complete demo
From the repository root:
pwsh samples/Gds/run-onboarding-demo.ps1
The script uses
opc.tcp://localhost:62560/OnboardingRegistrar by default. Select a
different port with:
pwsh samples/Gds/run-onboarding-demo.ps1 -Port 62660
Successful output ends with:
REGISTER Good Good
UNREGISTER Good
UNREGISTER_AGAIN BadNotFound
ONBOARDING_DEMO_OK
Pass -Keep to retain the temporary PKI stores and registrar logs for
diagnostics.
Run the applications manually
Start the registrar:
dotnet run --project samples/Gds/OnboardingRegistrar -f net10.0 -- `
--port 62560 `
--pkiRoot "$env:TEMP\opcua-onboarding-registrar"
After ONBOARDING_REGISTRAR_READY is printed, run the client:
dotnet run --project samples/Gds/OnboardingClient -f net10.0 -- `
--endpoint "opc.tcp://localhost:62560/OnboardingRegistrar" `
--pkiRoot "$env:TEMP\opcua-onboarding-client"
Wire contract
The client API uses the Part 21 types directly:
ArrayOf<StatusCode> results = await onboarding.RegisterTicketsAsync(
[
new ByteString(firstTicket),
new ByteString(secondTicket)
]);
EncodedTicket is a subtype of ByteString. The server returns one
StatusCode for every supplied ticket.
Security
The demo selects SignAndEncrypt with Basic256Sha256. For a
self-contained local run, both applications automatically accept the
other application's newly created certificate and store it under an
isolated temporary PKI root. The script also generates a one-run random
username and password, grants that user the generated RegistrarAdmin
role, and restricts the two ticket methods to that role. The endpoint
also permits an anonymous bootstrap session because ManagedSession
activates its configured identity provider after creating the session;
anonymous users have no permission to call the registrar methods.
Automatic certificate acceptance is a sample-only convenience. A production registrar must use managed trust lists, authenticate and authorize registrar administrators, persist tickets in a protected store, and apply the audit/redaction requirements described in the GDS developer guide.
For a manual run, set ONBOARDING_DEMO_USER and
ONBOARDING_DEMO_PASSWORD to the same non-empty values in both
terminals before starting either application. Do not reuse demo
credentials in a production deployment.