OPC UA Global Discovery Server (GDS)

July 16, 2026 · View on GitHub

This package implements the OPC UA Global Discovery Server (GDS) as defined in OPC 10000-12 (Part 12). It provides application registration, certificate lifecycle management (pull and push models), KeyCredentialService, AuthorizationService, and role-based access control.

TopicDocument
GDS core (this file)Directory, certificates, push management, roles
KeyCredentialServiceCredential issuance for non-UA services
AuthorizationServiceOAuth2-style access token issuance

Packages

NuGet PackageContents
Opc.Ua.Gds.CommonModel types, encodeable types, design files
Opc.Ua.Gds.Server.CommonServer-side node managers, providers, authorization
Opc.Ua.Gds.Client.CommonClient proxies for GDS, push, KeyCredential, AuthorizationService

1. Client API

GlobalDiscoveryServerClient

The primary client for interacting with a GDS. Manages its own session and exposes the full GDS directory and certificate management API.

using Opc.Ua.Gds.Client;

// Create and connect
var gdsClient = new GlobalDiscoveryServerClient(appConfig);
gdsClient.AdminCredentials = new UserIdentity("admin", "password");
await gdsClient.ConnectAsync("opc.tcp://gds-host:4840");

// Register an application
var appRecord = new ApplicationRecordDataType
{
    ApplicationUri = "urn:my-app",
    ApplicationType = ApplicationType.Server,
    ApplicationNames = new[] { new LocalizedText("My App") }.ToArrayOf(),
    DiscoveryUrls = new[] { "opc.tcp://my-app:4850" }.ToArrayOf()
};
NodeId appId = await gdsClient.RegisterApplicationAsync(appRecord);

// Request a new certificate (pull model)
NodeId requestId = await gdsClient.StartNewKeyPairRequestAsync(
    appId, default, ObjectTypeIds.RsaSha256ApplicationCertificateType,
    "CN=My App", domainNames, "PFX", null);

// Poll for completion
ByteString cert, privateKey;
ArrayOf<ByteString> issuerCerts;
do
{
    await Task.Delay(2000);
    (cert, privateKey, issuerCerts) =
        await gdsClient.FinishRequestAsync(requestId, default);
} while (cert.IsEmpty);

// Clean up
await gdsClient.DisconnectAsync();

ServerPushConfigurationClient

Client for OPC 10000-12 §7.10 push certificate management on a target server:

var pushClient = new ServerPushConfigurationClient(appConfig);
await pushClient.ConnectAsync(serverEndpoint);

// Push a new certificate
bool restartNeeded = await pushClient.UpdateCertificateAsync(
    pushClient.DefaultApplicationGroup,
    ObjectTypeIds.RsaSha256ApplicationCertificateType,
    newCertBlob, "PFX", privateKeyBlob, issuerCerts);

if (restartNeeded)
{
    await pushClient.ApplyChangesAsync();
}

// Create a self-signed certificate on the server
await pushClient.CreateSelfSignedCertificateAsync(
    pushClient.DefaultApplicationGroup,
    ObjectTypeIds.RsaSha256ApplicationCertificateType,
    "CN=NewSubject", domainNames);

2. Server-Side: Building a GDS

Minimal GDS Server

Use GlobalDiscoverySampleServer with dependency injection of your storage backends:

// 1. Create storage backends
IApplicationsDatabase database = JsonApplicationsDatabase.Load(
    "gds-applications.json", telemetry);
ICertificateGroup certGroup = new CertificateGroup(
    gdsConfig.AuthoritiesStorePath, gdsConfig.CertificateGroups);
IUserDatabase userDb = JsonUserDatabase.Load(
    "gds-users.json", telemetry);

// 2. Create the GDS server
// (database implements both IApplicationsDatabase and ICertificateRequest)
var gdsServer = new GlobalDiscoverySampleServer(
    database, database, certGroup, userDb, telemetry,
    autoApprove: true);

// 3. Start via ApplicationInstance
var app = new ApplicationInstance(telemetry)
{
    ApplicationName = "My GDS",
    ApplicationType = ApplicationType.Server,
    ConfigSectionName = "Opc.Ua.GlobalDiscoveryServer"
};
var config = await app.LoadAsync("MyGds.Config.xml");
await app.CheckApplicationInstanceCertificatesAsync(false);
await app.StartAsync(gdsServer);

Using GdsNodeManagerFactory

For embedding GDS into an existing server:

// In your server's CreateMasterNodeManager:
var gdsConfig = configuration.ParseExtension<GlobalDiscoveryServerConfiguration>();
var factory = new GdsNodeManagerFactory(gdsConfig);
additionalNodeManagers.Add(factory.Create(server, configuration));

Extension Points

Every pluggable interface and its purpose:

InterfacePurposeDefault
IApplicationsDatabaseApplication registration storeLinqApplicationsDatabase / JsonApplicationsDatabase
ICertificateRequestCertificate request lifecycleLinqApplicationsDatabase
ICertificateGroupCA operations (sign, revoke)CertificateGroup
IGdsUserDatabaseUser store + ApplicationAdmin bindingsImplement over LinqUserDatabase
IKeyCredentialRequestStoreKeyCredential lifecycleInMemoryKeyCredentialRequestStore
ISecretStoreSecret storage for credentialsInMemorySecretStore
IAccessTokenProviderToken issuance (AuthorizationService)null (Bad_NotSupported)
IConfigurationDataStoreManagedApplications config persistenceInMemoryConfigurationDataStore
IManagedApplicationsNodeManagerManagedApplications folderStubManagedApplicationsNodeManager

3. Implementing Providers

IApplicationsDatabase

Stores registered application records. Must also implement ICertificateRequest for certificate request lifecycle (or provide a separate implementation):

public class SqlApplicationsDatabase
    : ApplicationsDatabaseBase, ICertificateRequest
{
    // ApplicationsDatabaseBase provides Match() and ValidateApplication()
    // Override the abstract CRUD methods:

    public override NodeId RegisterApplication(
        ApplicationRecordDataType application)
    {
        ValidateApplication(application); // call base validation
        return _db.InsertApplication(application);
    }

    public override void UpdateApplication(
        ApplicationRecordDataType application)
    {
        ValidateApplication(application);
        _db.UpdateApplication(application);
    }
    // ... implement remaining abstract methods
}

ICertificateGroup

Implement to use an external CA (EST, ACME, EJBCA, etc.):

public class EstCertificateGroup : ICertificateGroup
{
    public async Task<(Certificate, byte[], CertificateCollection)>
        NewKeyPairRequestAsync(
            ApplicationRecordDataType application,
            string subjectName,
            string[] domainNames,
            string privateKeyFormat,
            string privateKeyPassword)
    {
        // Call your EST server's /simpleenroll endpoint
        // Return (signedCert, privateKey, issuerChain)
    }
    // ... implement remaining interface methods
}

IGdsUserDatabase

Extend IUserDatabase to support the ApplicationAdmin role (OPC 10000-12 §7.2). The GDS sample server automatically detects IGdsUserDatabase during username authentication:

public class MyGdsUserDatabase : LinqUserDatabase, IGdsUserDatabase
{
    private readonly Dictionary<string, List<NodeId>> _appAdminMap = new();

    public IReadOnlyList<NodeId>? GetAdministeredApplicationIds(
        string userName)
    {
        return _appAdminMap.TryGetValue(userName, out var ids)
            ? ids : null;
    }

    // Admin API to grant ApplicationAdmin for specific apps:
    public void GrantApplicationAdmin(
        string userName, IEnumerable<NodeId> applicationIds)
    {
        _appAdminMap[userName] = applicationIds.ToList();
    }
}

IConfigurationDataStore

Persistence backend for ManagedApplications (§7.10.16). The DefaultManagedApplicationsNodeManager uses this to populate ApplicationConfigurationState nodes:

public class FileConfigurationDataStore : IConfigurationDataStore
{
    public async ValueTask<IReadOnlyList<ManagedApplicationInfo>>
        GetManagedApplicationsAsync(CancellationToken ct)
    {
        // Read from config file / database
    }

    public async ValueTask<uint> WriteConfigurationAsync(
        string applicationUri, byte[] data,
        uint currentVersion, CancellationToken ct)
    {
        // Persist with optimistic concurrency check
    }

    public async ValueTask ConfirmUpdateAsync(
        string applicationUri, uint configVersion,
        CancellationToken ct)
    {
        // Mark the configuration as applied
    }
}

4. Roles and Authorization

GDS Roles (OPC 10000-12 §7.2)

RolePurpose
DiscoveryAdminRegister/update/unregister any application
CertificateAuthorityAdminManage certificate requests and trust lists
RegistrationAuthorityAdminApprove/reject certificate requests
ApplicationSelfAdminManage own application's registration
ApplicationAdminManage a configured set of applications

ApplicationSelfAdmin Privilege

GdsApplicationSelfAdminProvider grants GdsRole.ApplicationSelfAdmin when the secure-channel client certificate matches a certificate stored for the registered ApplicationRecordDataType found by the channel ApplicationUri. The provider is an IIdentityAugmenter, so it layers SelfAdmin on top of any accepted identity instead of using a custom ImpersonateUser callback.

For dependency-injection-hosted GDS servers the provider is registered with the symmetric builder extension:

services.AddOpcUa()
    .AddGdsServer(opt => opt.ApplicationName = "MyGds")
    .AddDefaultIdentityAuthenticators(opt => opt.EnableJwt = false)
    .AddGdsApplicationSelfAdminProvider();

AddDefaultIdentityAuthenticators(...) on IGdsServerBuilder also adds this provider so standard GDS hosts get OPC 10000-12 §7.2 SelfAdmin behavior automatically.

ApplicationAdmin Privilege

The ApplicationAdmin role allows a user to administer a specific set of applications (not all applications like DiscoveryAdmin).

How it works:

  1. Assign the GdsRole.ApplicationAdmin role to the user via IUserDatabase.CreateUser()
  2. Implement IGdsUserDatabase.GetAdministeredApplicationIds() to return the ApplicationIds the user may administer
  3. During username authentication, GlobalDiscoverySampleServer constructs a GdsRoleBasedIdentity with AdministeredApplicationIds populated
  4. AuthorizationHelper.CheckApplicationAdminPrivilege() verifies the target application is in the user's administered set
// Setup: create a user with ApplicationAdmin role
userDb.CreateUser("app-admin", passwordBytes,
    [GdsRole.ApplicationAdmin]);

// If using IGdsUserDatabase:
((MyGdsUserDatabase)userDb).GrantApplicationAdmin(
    "app-admin",
    [registeredAppId1, registeredAppId2]);

Security: Fail-Closed Channel Validation

AuthorizationHelper.HasAuthenticatedSecureChannel() enforces that all GDS methods are called over a signed (or encrypted) secure channel. The check fails closed — if the system context cannot be validated, the method throws BadSecurityModeInsufficient.


5. End-to-End Example

Complete example: register an application, get a certificate, push it to the server.

// ── 1. Start the GDS Server ─────────────────────────

var database = JsonApplicationsDatabase.Load("apps.json", telemetry);
var certGroup = new CertificateGroup(authStorePath, certGroupConfigs);
var userDb = JsonUserDatabase.Load("users.json", telemetry);

var gds = new GlobalDiscoverySampleServer(
    database, database, certGroup, userDb, telemetry);

var app = new ApplicationInstance(telemetry) { ... };
await app.StartAsync(gds);

// ── 2. Client: Register and Get Certificate ─────────

var gdsClient = new GlobalDiscoveryServerClient(clientConfig);
gdsClient.AdminCredentials = new UserIdentity("admin", "password");
await gdsClient.ConnectAsync("opc.tcp://localhost:58810");

// Register
var record = new ApplicationRecordDataType
{
    ApplicationUri = "urn:my-server",
    ApplicationType = ApplicationType.Server,
    ApplicationNames = new[] { new LocalizedText("My Server") }.ToArrayOf(),
    DiscoveryUrls = new[] { "opc.tcp://my-server:4840" }.ToArrayOf()
};
NodeId appId = await gdsClient.RegisterApplicationAsync(record);

// Request certificate
NodeId reqId = await gdsClient.StartNewKeyPairRequestAsync(
    appId, default,
    ObjectTypeIds.RsaSha256ApplicationCertificateType,
    "CN=My Server,O=My Org",
    new[] { "my-server", "localhost" }.ToArrayOf(),
    "PFX", null);

// Wait for completion
ByteString cert, key;
ArrayOf<ByteString> issuers;
do
{
    await Task.Delay(2000);
    (cert, key, issuers) =
        await gdsClient.FinishRequestAsync(reqId, default);
} while (cert.IsEmpty);

// ── 3. Push Certificate to Target Server ────────────

var pushClient = new ServerPushConfigurationClient(clientConfig);
await pushClient.ConnectAsync(serverEndpoint);

bool needsRestart = await pushClient.UpdateCertificateAsync(
    pushClient.DefaultApplicationGroup,
    ObjectTypeIds.RsaSha256ApplicationCertificateType,
    cert, "PFX", key, issuers);

if (needsRestart)
{
    await pushClient.ApplyChangesAsync();
}

// ── 4. Clean Up ─────────────────────────────────────

await pushClient.DisconnectAsync();
await gdsClient.DisconnectAsync();

Conformance Matrix

Status key: ✅ Implemented · ⚠️ Partial · 🔲 Optional, provider-gated (exposed only when configured) · ❌ Not implemented · N/A Not applicable

This matrix links every implemented OPC 10000-12 (Part 12) requirement in the ServerConfiguration / Push Certificate Management, TrustList, certificate-alarm, KeyCredentialService and AuthorizationService areas to the source that implements it and to the automated tests that exercise it. Clause numbers are OPC 10000-12 v1.05.07. When Part 12 behaviour changes, keep the matrix truthful: every new Method, alarm or optional child gets a row with its source and at least one test, and any behaviour that regresses to partial/unsupported is downgraded here. The push/transaction behaviour summarised below is documented in depth in CertificateManager.md → PushManagement Transactions.

Applicable Part 12 profiles and conformance units

The server-side Part 12 features map to these Facets in the OPC UA profile registry (profiles.opcfoundation.org, profile group UACore 1.05). Conformance units are marked (M)andatory or (O)ptional as declared by the Facet.

Facet (UACore 1.05)Key conformance unitsRepo status
Global Certificate Management Server Facet (Server/GlobalCertificateManagement)Pull or Push Model (M); Push Model for Global Certificate and TrustList Management (O); Pull Model for Global Certificate and TrustList Management (O)✅ Push model (ServerConfiguration); ✅ Pull model (GDS CertificateDirectory)
A & C CertificateExpiration 2022 Server Facet (Server/ACCertificateExpiration2022)A & C CertificateExpiration, A & C Alarm, A & C Acknowledge (M); A & C Comment / Confirm / Shelving (O)✅ mandatory CUs; optional Comment/Confirm/Shelving inherited from the base condition model
KeyCredential Service Server Facet (Server/KeyCredentialManagement)Push Model for KeyCredential Service, KeyCredential Authentication Mechanism Support (M); ProfileURI – MQTT UserName / AMQP SASL Plain / UA transport UserName (O)✅ push model + client proxy; ProfileURIs are provider-defined
Global Service Authorization Request Server Facet (Server/GlobalServiceAuthorization)GDS Authorization Service Server (M)✅ token issuance (AuthorizationServiceManager)
Authorization Service Server Facet (Server/AuthorizationServiceConfiguration)Authorization Service Configuration Server (M)⚠️ access-token validation is provided through pluggable Identity Providers; the Part 12 ServerConfiguration.AuthorizationServices configuration folder is not exposed
Global Discovery and Certificate Mgmt 2022 Server (Server/GlobalDiscoveryAndCertificateManagement2022)GDS Application Directory, GDS Certificate Manager Pull Model, GDS Query Applications, GDS LDS-ME Connectivity (M)✅ full GDS server

Automated verification

The automated conformance checks that run in CI are the NUnit suites: the unit suites in Opc.Ua.Server.Tests (address-space exposure, transaction coordinator, TrustList staging, certificate alarms, optional surface) and the end-to-end GDS integration suites in Opc.Ua.Gds.Tests — notably PushTest.cs, which drives a live push server through the ServerConfiguration Methods, and TrustListValidationTest.cs. The applicable Facets / conformance units above were identified with the OPC UA profile-graph tooling against profiles.opcfoundation.org.

There is no fully-automated CTT harness in this repository, and the formal OPC UA Compliance Test Tool (UACTT / CTT) — a licensed OPC Foundation corporate-member GUI application — was not run to produce this matrix; no CTT results are claimed here. Run it against the reference server (samples/ConsoleReferenceServer; see samples/README.md) for formal certification. Known UACTT script defects encountered in prior manual runs are catalogued in plans/ctt-issues.md.

GDS Directory (§6.5)

SectionFeatureStatusSourceTests
§6.5.3DirectoryTypeApplicationsNodeManager.csClientTest.cs, GdsApplicationDirectoryTests.cs
§6.5.4FindApplicationsOnFindApplicationsGdsApplicationDirectoryTests.cs
§6.5.5ApplicationRecordDataType / rcp+ rulesApplicationsDatabaseBase.ValidateApplicationApplicationsDatabaseBaseTests.cs, LinqApplicationsDatabaseTests.cs
§6.5.6RegisterApplicationOnRegisterApplication + DiscoveryAdminOrAppAdminGdsApplicationDirectoryTests.cs, RegisteredApplicationTests.cs
§6.5.7UpdateApplicationOnUpdateApplication + DiscoveryAdminOrSelfAdminOrAppAdminGdsApplicationDirectoryTests.cs
§6.5.8UnregisterApplicationOnUnregisterApplicationAsync + cert revocationGdsApplicationDirectoryTests.cs, ClientTest.cs
§6.5.9GetApplicationOnGetApplicationGdsApplicationDirectoryTests.cs
§6.5.10QueryApplicationsOnQueryApplicationsGdsQueryApplicationsTests.cs
§6.5.11QueryServersOnQueryServersGdsQueryApplicationsTests.cs, ClientTest.cs
§6.5.12ApplicationRegistrationChangedAuditEventAuditEvents.ReportApplicationRegistrationChangedAuditEventAuditEventsTests.cs

Certificate Management — Pull Model (§7.9)

Pull Certificate Management moved from §7.6 (1.04) to §7.9 (v1.05.07); the CertificateDirectoryType Methods below use the current clause numbers.

SectionFeatureStatusSourceTests
§7.9.3StartSigningRequestOnStartSigningRequestAsync + auditGdsCertificateManagementTests.cs, ClientTest.cs
§7.9.4StartNewKeyPairRequestOnStartNewKeyPairRequest + auditGdsCertificateManagementTests.cs, ClientTest.cs
§7.9.5FinishRequestOnFinishRequestAsync + issuer chainGdsCertificateManagementTests.cs, ClientTest.cs
§7.9.6RevokeCertificateOnRevokeCertificateAsync + auditGdsCertificateManagementTests.cs, CertificateGroupTests.cs
§7.9.7GetCertificateGroupsOnGetCertificateGroupsClientTest.cs, CustomCertificateGroupIntegrationTest.cs
§7.9.8GetCertificatesOnGetCertificatesGdsCertificateManagementTests.cs
§7.9.9GetTrustListOnGetTrustListClientTest.cs, CustomCertificateGroupIntegrationTest.cs
§7.9.10GetCertificateStatusOnGetCertificateStatusClientTest.cs
§7.9.11CheckRevocationStatusOnCheckRevocationStatusAsync + ValidityTimeGdsCertificateManagementTests.cs

Roles and Privileges (§7.2)

FeatureStatusSourceTests
DiscoveryAdminGdsRole.DiscoveryAdminAuthorizationHelperTests.HasAuthorizationSucceedsWhenUserHasDiscoveryAdminRole
CertificateAuthorityAdminGdsRole.CertificateAuthorityAdminAuthorizationHelperTests.HasAuthorizationSucceedsWithCertificateAuthorityAdminRole
RegistrationAuthorityAdminGdsRole.RegistrationAuthorityAdminAuthorizationHelperTests.cs
ApplicationSelfAdminGdsRole.ApplicationSelfAdminAuthorizationHelperTests.HasAuthorizationSucceedsWithSelfAdminForOwnApplication, ...ThrowsWithSelfAdminForDifferentApplication
ApplicationAdminGdsRole.ApplicationAdmin + AdministeredApplicationIdsAuthorizationHelperTests.cs, GdsApplicationSelfAdminProviderTests.cs
BadSecurityModeInsufficient (channel)AuthorizationHelper.HasAuthenticatedSecureChannelAuthorizationHelperTests.cs (asserts BadSecurityModeInsufficient), RoleAuthorizationGateTests.cs

Push Management — ServerConfiguration (§7.10)

ConfigurationNodeManager implements the full push transaction model: every certificate/TrustList Method stages work that only takes effect at ApplyChanges. Clause numbers are corrected to v1.05.07 (the 1.04 numbering was §7.7.x).

SectionFeatureStatusSourceTests
§7.10.5UpdateCertificateConfigurationNodeManager.UpdateCertificateAsync (staged; ApplicationCertificate TrustList validation; supplied issuers ignored for app groups)ConfigurationNodeManagerPushTests (UpdateCertificateWith*, ...IgnoresSuppliedIssuerCertificatesAsync), PushTest (UpdateCertificate*)
§7.10.6CreateSelfSignedCertificate...CreateSelfSignedCertificateAsync (occupied slot → Bad_InvalidState; subject/SAN/key-size validation)ConfigurationNodeManagerPushValidationTests (CreateDefaultApplicationCertificate*, ValidateKeySize*), ConfigurationNodeManagerPushTests (CreateSelfSignedCertificate*)
§7.10.7DeleteCertificate...DeleteCertificateAsync — empty slot → Bad_InvalidState; endpoint-reference check enforced at ApplyChangesConfigurationNodeManagerPushTests (DeleteCertificate*, DeleteCertificateReferencedByEndpointIsRejectedByApplyChangesAsync, RotatedCertificateBecomesEndpointReferenced*)
§7.10.8GetCertificates...GetCertificates (occupied slots only, aligned pairs)ConfigurationNodeManagerPushValidationTests.SelectOccupiedCertificateSlotsOmitsEmptySlotsAndPreservesOrder, ConfigurationNodeManagerPushTests.GetCertificates*, PushTest.GetCertificatesAsync
§7.10.9ApplyChanges...ApplyChanges (ordered commit + reverse rollback)PushConfigurationTransactionCoordinatorTests (ApplyChanges*), PushTest (ApplyChangesAsync, ApplyChangesForcesChannelRenegotiateAsync)
§7.10.10CreateSigningRequest...CreateSigningRequestAsync (nonce ≥ 32 bytes genuinely mixed in)ConfigurationNodeManagerPushTests (CreateSigningRequest*, ...WithRegenerateAndShortNonceThrowsBadInvalidArgument), PushTest.CreateSigningRequest*
§7.10.11CancelChanges...CancelChanges (discard; Bad_RequestCancelledByClient diagnostics)PushConfigurationTransactionCoordinatorTests.CancelChanges*, ConfigurationNodeManagerPushTests.CancelChanges*
§7.10.12GetRejectedList...GetRejectedListConfigurationNodeManagerPushTests.GetRejectedList*, PushTest.GetRejectedListAsync

Per-Method SecureChannel/Role enforcement (Bad_SecurityModeInsufficient for the channel vs Bad_UserAccessDenied for the Role), the §7.10.10 Nonce entropy requirement, and the §7.10.7 endpoint-reference rule are described in CertificateManager.md → PushManagement Transactions → Method security and validation.

Transaction model, diagnostics and lifecycle (§7.10.2, §7.10.9, §7.10.11, §7.10.17)

RequirementStatusSourceTests
§7.10.2 transaction lifecycle: single owning Session; other Sessions get Bad_TransactionPending; ownership cleared atomically on applyPushConfigurationTransactionCoordinatorPushConfigurationTransactionCoordinatorTests (StageStartsTransactionOwnedByStagingSession, StageFromAnotherSessionThrowsBadTransactionPending, ApplyChangesClearsOwnershipAtomicallySoANewSessionCanImmediatelyStageAsync)
§7.10.2 SupportsTransactions = true exposedConfigurationNodeManager.CreateServerConfigurationConfigurationNodeManagerPushTests.SupportsTransactionsIsExposedAndTrue
Staging blocked during an in-flight commit (Bad_InvalidState same-owner / Bad_TransactionPending other Session)coordinator commit guardPushConfigurationTransactionCoordinatorTests (StageFromOwningSessionDuringInFlightCommitReturnsBadInvalidStateThenSucceedsAsync, StagingFromAnotherSessionDuringAnInFlightCommit...)
§7.10.9 ordered commit, reverse-order rollback, Bad_NothingToDo, open-writer Bad_InvalidStatecoordinator prepare/commit/compensatePushConfigurationTransactionCoordinatorTests (ApplyChangesCommitsOperationsInRequestOrderAsync, ApplyChangesWithFailingCommitRollsBackEarlierOperationsInReverseOrderAsync, ApplyChangesWithNoActiveTransactionReturnsBadNothingToDoAsync, ApplyChangesWithOpenTrustListWriterReturnsBadInvalidStateWithoutClearingTransactionAsync)
§7.10.11 wrong-Session apply/cancel → Bad_SessionIdInvalid; Session-close cancels its transactioncoordinator ownership checksPushConfigurationTransactionCoordinatorTests (ApplyChangesFromWrongSessionReturnsBadSessionIdInvalidAsync, CancelChangesFromWrongSessionReturnsBadSessionIdInvalid, CancelForSessionClose*)
§7.10.17 TransactionDiagnostics DataValue status: Bad_OutOfService before any transaction, Bad_InvalidState while active, Good after completionConfigurationNodeManager diagnostics binding + coordinator snapshotConfigurationNodeManagerPushTests (TransactionDiagnosticsReportBadOutOfServiceBeforeAnyTransactionAsync, ...BadInvalidStateWhileTransactionActiveAsync, ...GoodResultAfterCompletedTransactionAsync), PushConfigurationTransactionCoordinatorTests.GetSnapshot*
§7.10.10 nonce entropy genuinely mixed into regenerated key (HMAC-DRBG)AdditionalEntropyCertificateKeyGeneratorAdditionalEntropyCertificateKeyGeneratorTests.cs
§7.10.10 regenerated private key persisted across Sessions and restartDirectoryPendingCertificateKeyStore / InMemoryPendingCertificateKeyStorePendingCertificateKeyStoreTests.cs
Coordinator scoped per server (no cross-server sharing); injected coordinator honoredConfigurationNodeManager private per-server defaultConfigurationNodeManagerPushTests (EachConfigurationNodeManagerOwnsADistinctDefaultCoordinator, AnInjectedCoordinatorIsSharedAcrossConfigurationNodeManagers)
Deferred post-apply effects drained/cancelled on shutdownConfigurationNodeManager.DeleteAddressSpaceAsync / DisposeConfigurationNodeManagerShutdownTests.cs

Optional ServerConfiguration surface (§7.10.13, §7.10.14, §7.10.20)

These optional members are exposed only when their backing option/provider is configured (see CertificateManager.md → Optional ServerConfiguration Surface); otherwise the node is suppressed.

Member (§)StatusSourceTests
Identity variables ApplicationUri / ProductUri / ApplicationType / ApplicationNames (§7.10.3)✅ alwaysServerConfigurationOptions from ApplicationConfigurationServerConfigurationSurfaceTests.IdentityPropertiesAlwaysExposedAndValuedAsync
HasSecureElement / InApplicationSetup (§7.10.3)🔲 provider-gatedServerConfigurationOptionsServerConfigurationSurfaceTests (HasSecureElementExposedWithValueWhenConfiguredAsync, InApplicationSetupExposedWithValueWhenConfiguredAsync, OptionalMembersSuppressedWhenNotConfiguredAsync)
ResetToServerDefaults (§7.10.13)🔲 provider-gatedIServerConfigurationResetProviderServerConfigurationSurfaceTests.ResetToServerDefaultsExposedOnlyWhenProviderConfiguredAsync, ServerConfigurationResetTests.cs
ConfigurationFile / ApplicationConfigurationFileType incl. CloseAndUpdate / ConfirmUpdate (§7.10.20)🔲 provider-gatedIApplicationConfigurationFileProvider + ApplicationConfigurationFile.csServerConfigurationSurfaceTests (ConfigurationFileExposedWithChildrenWhenProviderConfiguredAsync, ConfigurationFilePropertiesSeededFromProviderAsync, ConfigurationFileReadReturnsProviderContentAsync)

GDS-managed applications proxy (§7.10.14–§7.10.16)

The GDS-side proxy that exposes other applications' configurations under a ManagedApplications folder is a separate, partially-implemented feature (out of scope for a self-managed push Server).

SectionFeatureStatusSource
§7.10.14ApplicationConfigurationType⚠️DefaultManagedApplicationsNodeManager creates ApplicationConfigurationState nodes; IConfigurationDataStore abstracts persistence
§7.10.16ManagedApplications folder⚠️DefaultManagedApplicationsNodeManager; optimistic concurrency via version tracking

TrustList (§7.8)

SectionFeatureStatusSourceTests
§7.8.2.2TrustListType / Open / OpenWithMasks (Read + Write+EraseExisting only)Core TrustList.csTrustListTests.OpenWithMasksSyncAllReturnsGood
§7.8.2.5CloseAndUpdate — staged, applied at ApplyChangesTrustList.cs + coordinator; TrustListUpdatedAuditEvent post-commitTrustListTransactionTests (CloseAndUpdateStagesInsteadOfApplyingImmediatelyAsync, ...RollsBackWhenALaterOperationFailsAsync, ...SelfCompensatesWhenOnlyOneOfSeveralStoresFailsAsync), PushTest.UpdateTrustListAsync
§7.8.2.6/.7AddCertificate / RemoveCertificate — stagedTrustList.csPushTest (AddRemoveCertAsync, AddRemoveCATrustedCertAsync, AddRemoveCAIssuerCertAsync), TrustListTransactionTests.cs
§7.8.2Open-for-write blocked with Bad_TransactionPending while another Session's transaction is activeTrustList.cs + coordinatorTrustListTransactionTests.cs, PushConfigurationTransactionCoordinatorTests.ApplyChangesWithOpenTrustListWriter*
§7.8.2Read/write access controlTrustList.csTrustListTests (OpenReadWithoutReadAccessThrowsBadUserAccessDenied, OpenWriteWithoutWriteAccessThrowsBadUserAccessDenied)
§7.8.2LastUpdateTime set on init + after committed updateTrustList.csTrustListTransactionTests.cs
§7.8.2Writable / UserWritable; ActivityTimeout / DefaultValidationOptionsSet to true for GDS groups; generated from model CSVTrustListTests.cs
§8.4.5MaxTrustListSize advertised honestly + resource-protection safety ceiling; oversize → Bad_EncodingLimitsExceededTrustList.cs + ServerConfigurationOptions.MaxTrustListSizeSafetyCeilingTrustListValidationTest (NormalSizeTrustListAsync, WriteTrustListExceedsSizeLimit, TrustListJustUnderLimitAsync), ConfigurationNodeManagerPushTests.MaxTrustListSizeAdvertisesHonestFiniteEffectiveLimit

Certificate and TrustList alarms (§7.8.3)

Each CertificateGroup exposes the two optional standard alarm instances with full active/inactive transitions and events (previously property-only). ConfigurationNodeManager creates and wires them in CreateAddressSpace and StandardServer.OnServerStarted starts periodic monitoring (60 s) once the subscription infrastructure is ready; all thresholds and timers flow through the injected TimeProvider. See CertificateManager.md → Certificate-Expiration and TrustList-Staleness Alarms.

AlarmStatusSourceTests
CertificateExpirationAlarmType (CertificateExpired) — active within ExpirationLimit / expired, Medium/High severity, Retain, Acknowledge, cleared after certificate replacementCertificateGroupAlarmMonitor + ConfigurationNodeManager.EvaluateCertificateAlarms / StartAlarmMonitoringCertificateAlarmMonitoringTests (CertificateExpiredActivatesWithMediumSeverity*, ...HighSeverityWhenAlreadyExpired, ...DeactivatesAfterCertificateReplacement, RepeatedEvaluationsDoNotEmitDuplicateTransitionEvents, AcknowledgedAlarmClearsRetainAfterDeactivation, ClientAcknowledgeMethodMarksAlarmAcknowledged)
TrustListOutOfDateAlarmType (TrustListOutOfDate) — active when UpdateFrequency elapses, disabled when non-positivesameCertificateAlarmMonitoringTests (TrustListOutOfDateActivatesWhenStale, TrustListOutOfDateStaysInactiveWhenFresh, TrustListOutOfDateDisabledWhenUpdateFrequencyIsZero)

TrustList-change effects on channels and Sessions (§7.10.9)

RequirementStatusSourceTests
Committed TrustList change forces SecureChannel renegotiation (application group) and revalidates certificate user identities / closes invalidated Sessions (user-token group)PushConfigurationTrustListEffectHandler + ConfigurationNodeManagerConfigurationNodeManagerTrustListEffectsTests (BuildTrustListEffects*, ApplyChangesDispatches*EffectFor*, ApplyChangesUsesCommittedTargetsNotAConcurrentSessionsStagedTargetsAsync), PushConfigurationTrustListEffectHandlerTests.cs

Audit Events

Event TypeStatusSourceTests
ApplicationRegistrationChangedAuditEventRegister/Update/UnregisterAuditEventsTests.cs
CertificateRequestedAuditEventStartNewKeyPair + StartSigningAuditEventsTests.cs
CertificateDeliveredAuditEventFinishRequestAuditEventsTests.cs
CertificateRevokedAuditEventRevokeCertificateAuditEventsTests.cs
CertificateUpdateRequestedAuditEventUpdateCertificate (push)AuditEventsTests.cs, PushTest.cs
TrustListUpdatedAuditEventCore TrustList.csTrustListTransactionTests.cs, PushTest.cs
KeyCredentialRequestedAuditEventStartRequestAuditEventsTests.cs
KeyCredentialDeliveredAuditEventFinishRequestAuditEventsTests.cs
KeyCredentialRevokedAuditEventRevokeAuditEventsTests.cs
AccessTokenIssuedAuditEventRequestAccessTokenAuthorizationService/RefreshTokenTests.cs
Secrets redacted from audit payloadsRedactedPrivateKeyPassword / RedactedPrivateKeyAuditEventsTests.cs

KeyCredentialService (§8)

FeatureStatusSourceTests
StartRequestOnKeyCredentialStartRequestKeyCredentialRequestStoreTests.StartRequestReturnsNonNullIdAsync
FinishRequestOnKeyCredentialFinishRequestKeyCredentialRequestStoreTests (FinishRequestReturnsCredentialAsync, FinishRequestWithCancelRejectsRequestAsync, FinishRequestWithUnknownIdThrows)
RevokeOnKeyCredentialRevokeKeyCredentialRequestStoreTests (RevokeMarksCredentialAsRejectedAsync, RevokeUnknownCredentialThrows)
InMemoryKeyCredentialRequestStoreIKeyCredentialRequestStore.csKeyCredentialRequestStoreTests.cs
Client proxyKeyCredentialServiceClient.csKeyCredentialRequestStoreTests.cs
KeyCredential Push bindingWithKeyCredentialPush / KeyCredentialPushSubjectConfigurationNodeManagerPushTests (BindKeyCredentialPushWithExistingFolderBindsSubjectAsync, BindKeyCredentialPushWithNullSubjectThrowsArgumentNullException)

AuthorizationService (§9)

FeatureStatusSourceTests
GetServiceDescriptionOnGetServiceDescriptionAuthorizationService/RefreshTokenTests.GetServiceDescriptionReportsConfiguredServiceUri
RequestAccessToken (obsolete single-call)Legacy compatibility path via IAccessTokenProvider.RequestAccessTokenAsyncAuthorizationService/StartRequestTokenTests.LegacyRequestAccessTokenIssuesAnonymousUnprivilegedToken
StartRequestToken (RC)OnStartRequestToken / AuthorizationServiceManagerAuthorizationService/StartRequestTokenTests.cs
FinishRequestToken (RC)OnFinishRequestToken / AuthorizationServiceManagerAuthorizationService/StartRequestTokenTests.cs
RefreshToken (RC)OnRefreshTokenAsync / AuthorizationServiceManagerAuthorizationService/RefreshTokenTests.cs, AuthorizationService/InMemoryAccessTokenProviderRefreshTests.cs
SupportedRoles (RC)Model property exposed— (model property; not separately unit-tested)
Client proxyAuthorizationServiceClient.csAuthorizationService/StartRequestTokenTests.cs, AuthorizationService/RefreshTokenTests.cs

Refresh tokens

The default in-memory AuthorizationService provider issues a refresh token from FinishRequestToken and accepts it through RefreshToken (OPC 10000-12 §9.7). Refresh tokens are opaque, single-use secrets: each successful refresh consumes the current token, issues a new access token, and rotates to a new refresh token. The refresh-token lifetime slides by AuthorizationServiceOptions.DefaultRefreshTokenLifetime (7 days by default). Set AuthorizationServiceOptions.EnableRefreshTokens = false to preserve the legacy behavior where FinishRequestToken returns no refresh token and RefreshToken returns Bad_NotSupported.

var (newJwt, newJwtExpiresAt, newRefreshToken, newRefreshExpiresAt) =
    await authClient.RefreshTokenAsync(resourceId, refreshToken);

refreshToken = newRefreshToken;

Refresh-token audit events include only the resourceId input and the outcome. The refresh token itself is a secret and is never placed in audit payloads.

Remaining optional / unsupported Part 12 items

The following optional Part 12 members are not implemented (or only partially). None are required for the mandatory conformance units of the applicable Facets above; they are listed here so the matrix stays honest.

Item (§)StatusNotes
ServerConfiguration.AuthorizationServices folder (§9.6.2 / Authorization Service Configuration Server CU)❌ not exposedA push Server does not expose the configuration folder listing trusted Authorization Services. Client access-token validation is instead handled by the pluggable Identity Providers.
ManagedApplications / ApplicationConfigurationType proxy (§7.10.14–§7.10.16)⚠️ partialDefaultManagedApplicationsNodeManager creates the nodes with an IConfigurationDataStore; the full GDS-as-proxy update/confirm flow to remote managed applications is not complete.
KeyCredential authentication ProfileUris — AMQP SASL Plain / MQTT UserName / UA transport UserName (§8, optional CUs)🔲 provider-definedThe push binding is implemented; the concrete ProfileUri set advertised is defined by the credential provider, not fixed by the stack.
A & C CertificateExpiration optional CUs — Comment / Confirm / Shelving🔲 inheritedAvailable through the standard OPC 10000-9 condition model on the alarm instances; not separately exercised by the certificate-alarm tests.
Onboarding / OPC 10000-21 device setup interplay with ResetToServerDefaults / InApplicationSetup (§7.10.13, Annex G)🔲 provider-gatedThe stack exposes InApplicationSetup and defers the reset to IServerConfigurationResetProvider; it does not itself implement an OPC 10000-21 onboarding state machine.

LDS / LDS-ME (§4–5)

FeatureStatusSourceTests
FindServersLdsServer.FindServersAsyncLocalDiscoveryTests.cs
GetEndpointsLdsServer.GetEndpointsAsyncLocalDiscoveryTests.cs
RegisterServerLdsServer.RegisterServerAsyncLocalDiscoveryTests.cs
RegisterServer2LdsServer.RegisterServer2AsyncLocalDiscoveryTests.cs
FindServersOnNetworkLdsServer.FindServersOnNetworkAsyncLocalDiscoveryTests.cs, LdsServerStaticTests.cs
LDS-ME capabilityComputeServerCapabilitiesServerCapabilitiesTests.cs, ServerCapabilityTests.cs
mDNS _opcua-tcp._tcpMulticastDiscovery.OpcUaServiceTypeLdsServerStaticTests.cs
rcp+ reverse-connectMulticastDiscovery.ReverseConnectSchemeLdsServerStaticTests.cs
Annex C TXT keyspath / caps / rcLdsServerStaticTests.cs
Annex D identifiersLDS / LDS-MELdsServerStaticTests.cs

Extension Points (Phase 2 Abstractions)

Every extension point a user needs to provide or override:

InterfacePurposeDefault implUser replaces when...
IApplicationsDatabaseApplication registration storeLinqApplicationsDatabase / JsonApplicationsDatabaseUsing a SQL/NoSQL database
ICertificateRequestCert request lifecycleLinqApplicationsDatabaseCustom approval workflow
ICertificateGroupCA operationsCertificateGroupExternal CA (EST, ACME, etc.)
IKeyCredentialRequestStoreKeyCredential lifecycleInMemoryKeyCredentialRequestStorePersistent store, external IdP
ISecretStoreSecret storage for credentialsInMemorySecretStoreKey Vault, DPAPI, K8s secrets
IAccessTokenProviderToken issuance for AuthorizationServicenull (Bad_NotSupported)OAuth2/JWT provider
IConfigurationDataStoreManagedApplications config persistenceInMemoryConfigurationDataStoreFile-system, database
IManagedApplicationsNodeManagerManagedApplications folderStubManagedApplicationsNodeManager / DefaultManagedApplicationsNodeManagerFull push management
IConfigurationNodeManagerAlarm monitoringConfigurationNodeManager.StartAlarmMonitoringCustom thresholds/sources
IUserDatabaseUser/password storeExisting in GlobalDiscoverySampleServerLDAP, database