Repository navigation
Complete standalone PackML runtime and native async transition support - #4995
marcschier wants to merge 55 commits into
Conversation
Implement configurable modes, administration, fluent hosting, simulated sample and regression tests. Track completion in #4964. Server net10 build is verified; client/test execution and full validation remain pending.
Remove the cross-owner admission lease and execute complete operations inside the lifecycle owner. Reject same-owner asynchronous callback reentry before admission, including shutdown, and add deterministic regression scenarios. Validation awaits an individually cleared shared-host slot.
Complete the unit create lifecycle before the controller publishes its first state so state NodeIds use the PackML namespace that OnAfterCreate selects. Register PackML encodeable types in the node manager so method arguments decode to typed structures. Add the PackML namespace to the test context, and expect BadNotExecutable for non-executable wire commands as the Call service reports.
…dard into marcschier-packml-full-support # Conflicts: # docs/Profiles.md
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical issues risk unauthorized administration access, incomplete or inconsistent shutdown, and lost timed transitions, while missing assembly attributes cause analyzer-enabled build failures.
5 open issues
Add CLS compliance attributes to the new assemblies · New Shut down untracked unit before rollback removes its nodes · New Reject disposal reentry before shutting down committed units · New Preserve secure permissions when the configured role list is empty · New Retry timed transitions when admission is temporarily unavailable · New
What changed in this PR
Adds standalone OPC 30050 PackML hosting and typed clients alongside the existing Scales and Lens compositions. It also extends state-machine infrastructure with native asynchronous transition delivery. PackML remains Partial, with execution evidence and architectural sign-off pending.
Changes:
- Adds provider-backed runtime composition, configurable modes, administration accounting and client APIs.
- Adds async transition coordination, hierarchical synchronization and lifecycle notification handling.
- Adds a simulated client/server sample, regression coverage, package registration and documentation.
| File | Description |
|---|---|
UA.slnx |
Registers PackML projects, tests and sample. |
tests/Opc.Ua.Server.TestFramework/MonitoredNodeSamplingFence.cs |
Adds a notification-drain test helper. |
tests/Opc.Ua.Scales.Tests/ScalesNodeManagerTests.cs |
Adds controller compatibility and publication regressions. |
tests/Opc.Ua.PackML.Tests/PackMLTestSupport.cs |
Adds provider, clock and persistence test doubles. |
tests/Opc.Ua.PackML.Tests/PackMLStateMachineControllerTests.cs |
Covers async delivery, admission and customization. |
tests/Opc.Ua.PackML.Tests/PackMLAdministrationTests.cs |
Covers timers, persistence, counters and validation. |
tests/Opc.Ua.PackML.Tests/Opc.Ua.PackML.Tests.csproj |
Defines the PackML test project. |
tests/Opc.Ua.Aot.Tests/PackMLAotTests.cs |
Adds NativeAOT codec, runtime and factory coverage. |
tests/Opc.Ua.Aot.Tests/Opc.Ua.Aot.Tests.csproj |
References PackML assemblies for AOT tests. |
src/Opc.Ua.Server/StateMachines/StateMachineBuilder.cs |
Adds async hierarchy and lifecycle coordination. |
src/Opc.Ua.Server/StateMachines/FluentFiniteStateMachineState.cs |
Adds native async transitions and suspension handling. |
src/Opc.Ua.Server/Fluent/IStateMachineBuilder.cs |
Adjusts XML documentation references only. |
src/Opc.Ua.PackML/NugetREADME.md |
Links standalone PackML guidance. |
src/Opc.Ua.PackML.Server/PackMLUnitOptions.cs |
Defines and validates unit configuration. |
src/Opc.Ua.PackML.Server/PackMLServerOptions.cs |
Configures hosted units and startup callbacks. |
src/Opc.Ua.PackML.Server/PackMLRuntime.cs |
Composes units and manages runtime shutdown. |
src/Opc.Ua.PackML.Server/PackMLProvider.cs |
Defines equipment requests and provider contracts. |
src/Opc.Ua.PackML.Server/PackMLNodeManager.cs |
Hosts the model and standalone units. |
src/Opc.Ua.PackML.Server/PackMLLifetime.cs |
Serializes operations and coordinates shutdown. |
src/Opc.Ua.PackML.Server/Opc.Ua.PackML.Server.csproj |
Defines the server package. |
src/Opc.Ua.PackML.Server/NugetREADME.md |
Describes hosting and equipment completion. |
src/Opc.Ua.PackML.Server/Hosting/OpcUaPackMLServerBuilderExtensions.cs |
Adds fluent server registration. |
src/Opc.Ua.PackML.Server/Administration/PackMLAdministrationData.cs |
Defines administration and persistence contracts. |
src/Opc.Ua.PackML.Server/Administration/PackMLAdministration.cs |
Implements administration projection and timer accounting. |
src/Opc.Ua.PackML.Client/Properties/AssemblyInfo.cs |
Declares client assembly CLS compliance. |
src/Opc.Ua.PackML.Client/PackMLValue.cs |
Represents typed values and discovery results. |
src/Opc.Ua.PackML.Client/PackMLSnapshots.cs |
Defines status and administration read snapshots. |
src/Opc.Ua.PackML.Client/PackMLFields.cs |
Adds typed field paths and decoders. |
src/Opc.Ua.PackML.Client/PackMLClientFactory.cs |
Creates clients over borrowed sessions. |
src/Opc.Ua.PackML.Client/PackMLClient.cs |
Implements discovery, reads and observation. |
src/Opc.Ua.PackML.Client/PackMLClient.Commands.cs |
Routes commands through generated proxies. |
src/Opc.Ua.PackML.Client/Opc.Ua.PackML.Client.csproj |
Defines the client package. |
src/Opc.Ua.PackML.Client/NugetREADME.md |
Summarizes client capabilities and ownership. |
src/Opc.Ua.PackML.Client/Hosting/OpcUaPackMLClientBuilderExtensions.cs |
Registers the typed-client factory. |
src/Opc.Ua.Core.Types/Opc.Ua.Core.Types.csproj |
Grants internal access to PackML and core tests. |
samples/README.md |
Lists the PackML sample. |
samples/PackML/README.md |
Documents sample execution and security boundaries. |
samples/PackML/PackMLSample/SampleEquipment.cs |
Implements simulated equipment behavior. |
samples/PackML/PackMLSample/Program.cs |
Demonstrates hosting and typed-client operations. |
samples/PackML/PackMLSample/PackMLSample.csproj |
Defines the sample application. |
README.md |
Introduces standalone PackML support. |
docs/StateMachines.md |
Documents native async transition semantics. |
docs/Scales.md |
Clarifies standalone versus Scales composition. |
docs/README.md |
Adds the PackML guide entry. |
docs/Profiles.md |
Updates PackML capabilities while retaining Partial status. |
.azurepipelines/expected-packages.txt |
Adds expected client and server packages. |
Review effort: Balanced
Code coverage✅ Coverage gate passed.
Uncovered changed lines
Coverage is above the recorded baseline - consider ratcheting Thresholds live in |
There was a problem hiding this comment.
🟡 Changes recommended
Critical issues remain: the default localhost endpoint can expose anonymous equipment commands beyond loopback while accepting untrusted certificates, and callback-triggered disposal can leave the runtime partially shut down and permanently unable to add units.
2 open issues
5 resolved since last review
Retry timed transitions when admission is temporarily unavailable Preserve secure permissions when the configured role list is empty Reject disposal reentry before shutting down committed units Shut down untracked unit before rollback removes its nodes Add CLS compliance attributes to the new assemblies
Review effort: Balanced
A WoT mutation that holds admission or a decision window can await a node deletion on another flow. Deletion now removes a published owner's serving references without waiting and queues contribution pruning for the next admission holder or publication callback. Adds a facade-matrix regression that deletes a PackML unit from a decision callback.
…drains Queue contribution pruning before any await, retry serving removals rejected by a source node reserved for a pending publication, and drain both queues in the background once the decision window closes so retired owners are released. Adds a facade-matrix regression for reserved source references.
…rain A dedicated NodeReferencesReservedException separates reserved-source rejections from other provider failures. A non-reservation failure withdraws the pending contribution prune, or queues the unremoved references when publication already applied it. The drain clears its scheduled flag on every exit, keeps failed retries queued, and publication restarts it. Adds regressions for an unrelated provider failure and a drain fault.
A deletion or publication that requests a drain while one is running records the request. The running drain then makes another pass instead of stopping after a faulted pass, and an exit through an unexpected exception starts a new drain for the recorded request.
Avoid CA1508 on the nullable retry accumulator assigned only in catch blocks by creating the collection eagerly and checking its count. No analyzer suppression is added. Add a barrier-controlled regression that pauses a drain retry, completes another deletion while admission is held, and then faults the paused retry. Assert that the recorded request causes a second attempt and removes the retained reference without any further mutation. Local .NET validation remains on hold.
Retain type registration identities across publication and retry removals after the decision. Cover adapted source admission and the exact reservation exception.
…ull-support # Conflicts: # tests/Opc.Ua.Aot.Tests/Opc.Ua.Aot.Tests.csproj
…ull-support # Conflicts: # tests/Opc.Ua.Aot.Tests/Opc.Ua.Aot.Tests.csproj
There was a problem hiding this comment.
🟡 Changes recommended
Critical source-identity checks can reject valid facade-bound events and let emitters bypass hidden-facade gates, while the full PackML support claim lacks required executable conformance evidence.
2 open issues
Review effort: Balanced
| for (int ii = 0; ii < routing.NodeManagers.Length; ii++) | ||
| { | ||
| IAsyncNodeManager facade = routing.NodeManagers[ii]; | ||
| if (!ReferenceEquals(NodeManagerGenerationRegistry.GetIdentity(facade), resource)) |
# Description _Describe the changes here to communicate to the maintainers why they should accept this pull request. By default - this will become the Commit message after merging and thus define history._ Extend OPC 40200 Weighing Technology through the existing Scales runtime, typed client, fluent builders, DI/direct construction, and samples. Catchweigher and price-labeler processing accepts equipment-acquired items and equipment-supplied or injectable prices. Totalizing-hopper completed fills update statistics and TipCounter. Analog and Boolean-edge recipe waits are opt-in. Ordinary hopper/checkweigher behavior, manual recipes, PackML, source quality, and product locks remain supported. Physical sorter, printer and motor operation, fiscal certification, and optional MCP/UA Lens additions are excluded. **Support remains Partial. Do not merge.** Published successor `ce01effa15cea555c31561d4ab59d39948d6b19b` includes the reviewed four-file FileSystem lifecycle correction (`849223eda7ae03d500825dc3cc49183b1285eb46`) and merges current master `ab5fddad9ab8ba162f10766b0e685c882db31ba9`. [Exact-head CI 42531450874](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/42531450874) completed successfully on this commit, including the code-coverage job. The matched master-to-ce01 coverage comparison still shows decreases in the Scales.Server and Scales.Client components; see the coverage results below. A separate scoped recovery patch remains unrun and unmeasured, and is not part of this PR head. The existing ready state and merge settings are unchanged. The retained 299-line notification diagnostic block is neither committed nor pushed and is not treated as a regression suite for the selected ordinary-notification policy. The unapproved FileSystem documentation paragraph is excluded. **FileSystem lifecycle correction.** Session close previously marked an idle writer closing while deferred cleanup retained its write reservation. FileTypeBinder now starts idle memory-stream cleanup without provider-thread deferral. FileHandle signals completed asynchronous open only after its linked provider token is detached; it skips lifetime cancellation only for detached, idle asynchronous opens, while synchronous compatibility, pending opens, active operations and disposal retain cancellation. Active work keeps its reservation until it drains. No synchronous provider callback is introduced into the asynchronous path. **Paired Windows candidate evidence.** [Green diagnostic 42529482112](https://github.com/marcschier/UA-.NETStandard/actions/runs/42529482112), diagnostic head `9aea971fab7d4895306d5bce91060101a8bf8951` with immutable source parent `4ef034ae635d636a1db3b3c72964bc2ef24ad1a2`, passed `InlineSessionClosureWaitsForProviderTokenDetachmentAsync` 1/1 on native Windows net48 and net10.0. Full FileHandleLifetimeRegressionTests/FileTypeBinderLifetimeTests passed 53/53 net48 and 54/54 net10.0, with no fixture/RunInfo errors. [Red diagnostic 42529488811](https://github.com/marcschier/UA-.NETStandard/actions/runs/42529488811), diagnostic head `135523b6964029e75926eaa26206a03dcf074ef1` with source `0734c09cff268288a94c6dbdd3fc16e68c53ee87`, failed 0/1 on each TFM at the intended `Assert.That(stream.CanRead, Is.True)` assertion (actual False), not a timeout or build failure. Both red TRXs and all four green TRXs were independently inspected. These paired Windows candidate runs are separate from the completed exact-ce01 CI listed above and do not establish local full-solution validation. **User-selected category policy:** preserve existing mixed-category reporting and measured-only TotalPackagesWeighed totals; retain Partial until category participation is clarified. OPC 40200 [§7.20.1](https://reference.opcfoundation.org/specs/OPC-40200/7.20.1) and [§7.20.3](https://reference.opcfoundation.org/specs/OPC-40200/7.20.3) define Weighed as category participation, while [§7.19.3](https://reference.opcfoundation.org/specs/OPC-40200/7.19.3) permits rejections before or after weighing. The current dynamic Boolean describes measurement completeness and cannot represent the measured subset of a mixed category. No new split categories or incompatible-report rejection is imposed. Ordinary enum-notification reentry is preserved. **Validation on immutable fa65 code head.** Opc.Ua.Scales.Tests passed 616/616, zero failed/skipped, on [Windows net48](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/42499377576/job/128792906071), [Windows net10.0](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/42499377576/job/128792906131), [Linux net10.0](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/42499377576/job/128792906205), and [macOS net10.0](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/42499377576/job/128792909555), excluding LongRunning and Stress. All ten unit/currency/registered-reading ownership cases and six unsupported-copy cases passed. **Historical exact d6 outcome.** [CI 42513278502](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/42513278502) completed with failure on `d6e0a03f3fed85ac84a6e61f4382aaea36cc7024`. Completed logs verify Scales 616 passed, 0 failed, 0 skipped on [Windows net48](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/42513278502/job/128830867436), [Windows net10.0](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/42513278502/job/128830867459), [Linux net10.0](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/42513278502/job/128830867805), and [macOS net10.0](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/42513278502/job/128830870585). The sole failed test shard, Linux job 128830870599, failed `MachineVisionRuntimeTests.SharedFileBindingReleasesClosedSessionHandlesAsync(True)` with `BadNotReadable`: the file was locked for writing. Vision recorded 712 passed, 1 failed, 1 skipped; aggregate summary 128863789556 also failed. Both parameter cases passed on preceding fa65 Linux job 128792909544/TRX artifact 13627626176. Those differing outcomes alone do not establish a cause; the separate lifecycle investigation reproduced deferred cleanup retaining an idle writer reservation. The correction is published in ce01; the integrated exact-head CI completed successfully as linked above. **Coverage on historical d6.** [Coverage job 128863789514](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/42513278502/job/128863789514) completed successfully and reported "Coverage is above the recorded baseline" and "Coverage gate passed." Windows net10.0 Scales coverage was 793/827 covered lines for Scales.Client (95.89%), 186/186 for Scales (100%), and 6567/7021 for Scales.Server (93.53%). Line counts and branch rates matched fa65; this comparison establishes no decrease across the documentation successor, not against a pre-feature baseline. The passing coverage gate does not change d6's failed overall result. Exact-ce01 CI and matched component-coverage results are reported separately above; local full-solution validation remains unrun. **Matched Scales component coverage: master ab5 versus exact ce01.** Master run 42530395411 (artifact 13640808392) compared with exact-ce01 run 42531450874 (artifact 13642293626) shows Scales.Server line coverage decreasing from 3635/3833 (94.834%) to 6568/7021 (93.548%), while branch coverage rises from 1474/1842 (80.022%) to 2962/3595 (82.392%). Scales.Client line coverage decreases from 612/633 (96.682%) to 797/827 (96.372%), and branch coverage decreases from 214/273 (78.388%) to 263/341 (77.126%). Root combined coverage increased from 510227/576601 to 513513/580204 lines and from 219732/278244 to 221408/280233 branches; that aggregate increase does not erase the component-level decreases. The separate scoped regression recovery patch has not been run or measured and does not qualify as restored Scales coverage. **Overall fa65 CI failed.** macOS job 128792909513 failed QuietSubscriptionAcknowledgementSurvivesConcurrentPublishesAsync(4): BadPublishRequestCount was 1 instead of 0. A server BadTimeout was recorded during that interval, but its relation to the counter increment is unproven; the acknowledgement assertion passed. Separate immutable-fa65 hosted controls passed 12/12 in run 42513134265, and an instrumented full fixture passed 30/30 in run 42514537147. Neither reproduced the failure. A traced BadNoSubscription/counter increment occurred after a passing assertion during cleanup and does not explain the original failure. The zero assertion is retained. This failure is not attributed to weighing or dismissed as pre-existing. Checkweigher reports commit selected-product and generic-scale statistic caches before observers run. Synchronous observers remain inline before asynchronous observers; exceptions propagate without rolling back committed counts. Recorder initialization succeeds before caching, and successful product removal retires captured associations without independently retaining removed subtrees. Native nominal service writes recompute GiveAway within writer admission and transfer admission to derived-value notification delivery. Direct assignments remain application-serialized and refresh derived values on the next report. Sample deviation uses n-1; FullCheck uses n. Empty statistics and one-item samples publish zero placeholders with BadNoData; singleton FullCheck publishes computed zero with Good quality. GiveAway is `(acceptedMean - currentNominalWeight) * acceptedCount`, including negative results. Bad or uncertain nominal quality propagates. Infinite inputs reject before counters change. Mixed measured/unmeasured aggregates use the measured subset with UncertainSubNormal; overflow retains the previous finite value with BadOutOfRange. The equations were verified against the [VDMA 40200:2025-04 co-publication](https://www.vdma.eu/en/catalog-detail/-/catalog/14137), identified as identical to OPC 40200 Release 2.00, sections 7.19.3 and 7.20.3. The PDF and excerpts are not redistributed. Optional notification metadata includes AuxParameters, HelpSource and VendorNotificationId with absent-versus-empty behavior. Accepted lower-tolerance percentages use all accepted items, independently of optional counter nodes; each report refreshes percentages. NaN items do not increment TotalPackagesWeighed. Mixed-category Weighed remains the documented conformance limitation under the selected compatibility policy. Correlated initialization, reset and acquisition publication rejects same-flow nested writes with BadInvalidState rather than waiting for the publisher. External asynchronous writers wait. Initialization failure remains uncached and retryable before counting. Catchweigher product-period reset includes zone counters, StartTime/ResetCondition and percentages without resetting other products, scale totals or acquisition sequence ownership. No UA Reset method is added. **User-selected acquisition contract:** publication retains setter-by-setter ordering. Catchweigher consumes the sequence before the first output counter changes, or before product counting if no output statistic exists. Totalizing hopper consumes it before TipCounter changes. A late observer exception can leave partial published state, suppresses WeightRegistered, and rejects same-sequence retry with BadEntryExists without replaying counters. Early catchweigher LastItem observer failures before counting remain retryable. Applications reconcile partial state; no rollback or atomic acquisition snapshot is promised. **Ordinary enum notification reentry remains separate.** A write originating in any active ordinary notification flow may run while another callback is active; external writers wait. Preset notifications remain inline after the preset lock is released, with CurrentProducts before product subtrees and CurrentProductsChanged last. Exceptions propagate and failed change masks are restored without undoing preset mutation. No deferred peer-drain policy is implemented. The preserved reentry behavior does not isolate writes from overlapping callbacks; that limit and mixed-category Weighed retain the Partial rating. Native async Value writes preserve async-handler precedence and sparse enum validation. Unsupported custom/synchronous-only paths return BadNotSupported without calling synchronous handlers or changing the cache. UInt64 enum values above Int64.MaxValue reject before callbacks/cache mutation, following the recommendation in [Part 8 section 5.3.3.4](https://reference.opcfoundation.org/specs/OPC-10000-8/v1.05.07/5.3.3.4); the legacy synchronous fallback remains. **Breaking change:** public WriteAttributeAsync is non-virtual. Overrides move to protected WriteAttributeCoreAsync to run inside admission and call the protected base hook rather than reentering the public entrypoint. The user accepted this deviation from 1.5.378 compatibility; docs/MigrationGuide.md contains examples. Recipe starts retain product reservations without controller locks; replacing a pending release check, including A-to-B-to-A replacement, rejects start. Without PackML, explicit application release or an allowing check is required. **Metadata ownership.** CopyUnit detaches the unit's LocalizedText display name and description, and CopyCurrency detaches the currency text, preserving locale and translation fallback. RegisteredReading receives a separate engineering-unit value from the pending acquisition; scalar registration is unchanged. UnsupportedMetadataCopyDoesNotConsumeAcquisitionAsync covers unsupported formatting arguments and comparers across unit, equipment-currency, and provider-returned currency sources. All six cases passed: BadNotSupported propagates before publication, counts remain zero, and the same sequence with supported metadata counts once. Earlier 1e55 passed 600 compatibility cases and failed ten controls on all four platforms: six failures demonstrated aliases, while four were multilingual-fixture defects corrected explicitly with AsMultiLanguage. The corrected multilingual cases are passing controls, not previously established alias failures. See docs/Scales.md and docs/Profiles.md for APIs, ownership, publication and remaining limits. README/style changes from other sessions are untouched. ## Related Issues _Reference all GitHub issues this PR addresses. If there is no issue yet, open one and link it here._ _If this is a relatively large or complex change, a design must have been discussed in the related tracking issue and signed off (which becomes the Architectural Decision Record (ADR))._ - Fixes #4969. - Prerequisite #4960 is merged. - Shared condition/alarm setters are reviewed in #4992 and the PackML/FSM seam in #4995. Scales reuses ClearChangeMasksAsync rather than duplicating those implementations. ## Checklist _Put an `x` in the boxes that apply. You can complete these step by step after opening the PR._ - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [ ] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [ ] I have added all necessary documentation. - [ ] I have verified that my changes do not introduce (new) build or analyzer warnings. - [ ] If this PR changes C# source files, I ran **all** tests locally using the **UA.slnx** solution against at least .NET **Framework** and .NET **10**, and all passed. PRs changing only non-C# files do not require these .NET test runs. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. ## Validation commands Candidate diagnostics used Windows Server 2025, Debug. For each framework, the exact regression and full fixtures were separate successful invocations in green run 42529482112: - `& .\.github\scripts\run-dotnet-tests.ps1 -Projects 'tests\Opc.Ua.Server.Tests\Opc.Ua.Server.Tests.csproj' -CustomTestTarget 'net48' -Framework 'net48' -Configuration Debug -Filter 'FullyQualifiedName~Opc.Ua.Server.Tests.FileSystem.FileHandleLifetimeRegressionTests.InlineSessionClosureWaitsForProviderTokenDetachmentAsync' -HangTimeout '3m' -PerProjectTimeoutMinutes 10 -ResultsDirectory 'artifacts\vision-token\exact' -QuietOutput` — passed 1/1. Same command on red run 42529488811 failed 0/1 at the intended stream.CanRead assertion. - `& .\.github\scripts\run-dotnet-tests.ps1 -Projects 'tests\Opc.Ua.Server.Tests\Opc.Ua.Server.Tests.csproj' -CustomTestTarget 'net10.0' -Framework 'net10.0' -Configuration Debug -Filter 'FullyQualifiedName~Opc.Ua.Server.Tests.FileSystem.FileHandleLifetimeRegressionTests.InlineSessionClosureWaitsForProviderTokenDetachmentAsync' -HangTimeout '3m' -PerProjectTimeoutMinutes 10 -ResultsDirectory 'artifacts\vision-token\exact' -QuietOutput` — passed 1/1. Same command on red run 42529488811 failed 0/1 at the intended stream.CanRead assertion. - `& .\.github\scripts\run-dotnet-tests.ps1 -Projects 'tests\Opc.Ua.Server.Tests\Opc.Ua.Server.Tests.csproj' -CustomTestTarget 'net48' -Framework 'net48' -Configuration Debug -Filter 'FullyQualifiedName~Opc.Ua.Server.Tests.FileSystem.FileHandleLifetimeRegressionTests|FullyQualifiedName~Opc.Ua.Server.Tests.FileSystem.FileTypeBinderLifetimeTests' -HangTimeout '3m' -PerProjectTimeoutMinutes 10 -ResultsDirectory 'artifacts\vision-token\full' -QuietOutput` — passed 53/53. - `& .\.github\scripts\run-dotnet-tests.ps1 -Projects 'tests\Opc.Ua.Server.Tests\Opc.Ua.Server.Tests.csproj' -CustomTestTarget 'net10.0' -Framework 'net10.0' -Configuration Debug -Filter 'FullyQualifiedName~Opc.Ua.Server.Tests.FileSystem.FileHandleLifetimeRegressionTests|FullyQualifiedName~Opc.Ua.Server.Tests.FileSystem.FileTypeBinderLifetimeTests' -HangTimeout '3m' -PerProjectTimeoutMinutes 10 -ResultsDirectory 'artifacts\vision-token\full' -QuietOutput` — passed 54/54. The following exact commands appear in completed fa65 and d6 CI logs. Their historical Scales results are not the exact-ce01 CI result linked above and do not establish local full-solution validation. - Windows net48: `dotnet test tests/Opc.Ua.Scales.Tests/Opc.Ua.Scales.Tests.csproj --no-build --configuration Release --framework net48 /p:CustomTestTarget=net48 --logger trx --results-directory D:\a\UA-.NETStandard\UA-.NETStandard\TestResults\Opc.Ua.Scales.Tests --blame-hang-timeout 10m --blame-hang-dump-type mini --blame-crash --blame-crash-dump-type mini --filter TestCategory!=LongRunning&TestCategory!=Stress` — fa65 passed 616/616, 4m40s; d6 passed 616/616, 4m40s. - Windows net10.0: `dotnet test tests/Opc.Ua.Scales.Tests/Opc.Ua.Scales.Tests.csproj --no-build --configuration Release --framework net10.0 /p:CustomTestTarget=net10.0 --logger trx --results-directory D:\a\UA-.NETStandard\UA-.NETStandard\TestResults\Opc.Ua.Scales.Tests --blame-hang-timeout 10m --blame-hang-dump-type mini --blame-crash --blame-crash-dump-type mini /p:CollectCoverage=true --collect:XPlat Code Coverage --settings ./tests/coverlet.runsettings.xml --filter TestCategory!=LongRunning&TestCategory!=Stress` — fa65 passed 616/616, 3m44s; d6 passed 616/616, 3m26s. - Linux net10.0: `dotnet test tests/Opc.Ua.Scales.Tests/Opc.Ua.Scales.Tests.csproj --no-build --configuration Release --framework net10.0 /p:CustomTestTarget=net10.0 --logger trx --results-directory /home/runner/work/UA-.NETStandard/UA-.NETStandard/TestResults/Opc.Ua.Scales.Tests --blame-hang-timeout 10m --blame-hang-dump-type mini --blame-crash --blame-crash-dump-type mini /p:CollectCoverage=true --collect:XPlat Code Coverage --settings ./tests/coverlet.runsettings.xml --filter TestCategory!=LongRunning&TestCategory!=Stress` — fa65 passed 616/616, 3m11s; d6 passed 616/616, 3m22s. - macOS net10.0: `dotnet test tests/Opc.Ua.Scales.Tests/Opc.Ua.Scales.Tests.csproj --no-build --configuration Release --framework net10.0 /p:CustomTestTarget=net10.0 --logger trx --results-directory /Users/runner/work/UA-.NETStandard/UA-.NETStandard/TestResults/Opc.Ua.Scales.Tests --blame-hang-timeout 10m --blame-hang-dump-type mini --blame-crash --blame-crash-dump-type mini /p:CollectCoverage=true --collect:XPlat Code Coverage --settings ./tests/coverlet.runsettings.xml --filter TestCategory!=LongRunning&TestCategory!=Stress` — fa65 passed 616/616, 14m36s; d6 passed 616/616, 17m11s. Local full-solution commands `dotnet test UA.slnx -p:CustomTestTarget=net48 -m:1` and `dotnet test UA.slnx -p:CustomTestTarget=net10.0 -m:1` remain unrun. Admission attempts either stopped before mutex acquisition because RAM was below 12 GiB, or passed sustained memory checks but found the shared gate occupied on nonblocking acquisition. No local .NET command or mutex waiter started. Latest ce01 admission probe found 11.03 GiB available, below the required floor. The local-solution checklist remains unchecked; passing hosted candidate tests and historical coverage do not substitute for it. No local full-suite, all-warning clearance, or passing overall ce01 CI result is asserted.


Description
Add a standalone OPC 30050 PackML runtime, administration, typed clients, and a simulated client/server sample while retaining the Scales and Lens compositions. Provider acceptance remains separate from equipment completion. Modes define concrete state subsets; state Reset does not reset production counters.
Support status: The published head is not yet full PackML 1.01 support. A real-session browse found that the standalone unit is not organized under
PackMLObjectsas required by OPC 30050 §6.2. The folder fix and its regression are not yet published or validated.Native async state-machine operations deliver each transition's events and change masks before the next transition. While async notifications are being delivered, overlapping synchronous writes return
BadInvalidStateor throwServiceResultExceptionfor void setters. Composite operations coordinate parent and nested machines; hierarchical completion and fluent lifecycle observers use native async paths. Callbacks must not await reentrant operations that require the same owner. Legacy synchronous callers retain their behavior outside async notification delivery.Parentless units register as Server event roots, and deletion removes their registrations. During candidate preparation, Server
HasNotifierreferences remain private. Reference cleanup resolves the registered facade without changing generation ownership. The published PackML subscriber test passed for Direct, Adapted, and Forwarded managers, but separate Server forwarding and lifetime tests still fail. The routing and ownership changes are in progress and have not been validated. Those fixes are being developed in #5011, and this PR will align with the shared implementation before qualification.PackML evidence on published head
4f7a4ca68The PackML test project passed 220 of 220 cases on each of four platform and framework combinations. These results are for the published head and do not validate the unpublished lifecycle draft.
ConfiguredUnitAdvertisesEveryDefinedServerConformanceUnitchecks 40 distinct Table 63 server titles and the completion path. It does not establish each unit's full behavior. Passing real-session tests cover state-command acceptance and equipment completion, optional base properties, configured operations, and administration reads. BothPackMLMonitoredItemSupportsQueueSizeOfAtLeastFivecases and the optional Hold-transition cases also pass.The tests do not yet map every companion conformance requirement to executable evidence. In particular, PackML tests do not directly assert the typed-client available-state and transition arrays or read
CurMachSpeedengineering units over the wire. These are qualification gaps, not evidence that the features are absent. Inherited Core, Data Access, Method, transport, subscription, and time-synchronization requirements require separate qualification. The results are not OPC Foundation certification.Outstanding validation and limitations
StandaloneUnitIsOrganizedBelowPackMLObjectsAsyncfound the folder but itsOrganizesbrowse was empty. Diagnostic head32ff1f21e2895c61afe68f35e174758593196d62has published head4f7a4ca68dd924de3dcd82d6a6a71146da7575bcas its ancestor and changes only the diagnostic test and workflows. The one selected test failed; this establishes a functional gap, not certification or a result for the unpublished draft.PrivateForwardedRawBindingFollowsCandidateLifetimeAsynccases. The full Server project failed 13 tests on Windows net10.0 and Linux net10.0.UnavailableUpstreamEndpointFailsMappedReadAsyncwas active; its 64 completed tests passed. The same-head retry passed 72 of 72, including that case in 6 minutes 48 seconds. The retry does not explain the earlier timeout or validate the unpublished draft.dotnet test UA.slnxruns on net48 and net10.0 have not been performed. The shared host has not provided a conflict-free validation window. A previous SDK 10 solution run reported VSTest errors for the three TUnit NativeAOT projects before the job was cancelled; CI publishes and runs those executables separately. No alternative runner is represented as a full-solution result.c779de3dfailedNetworkModelIntegrationTests.RemovedInterfaceDisappearsAndOldNodeReturnsUnknownwithBadNodeIdUnknownduring discovery. The other 1,197 tests passed. The failure did not record the NodeId. Master also removes nodes from lookup before cleaning references, and the unchanged client traverses in separate browse calls. The cause and PR attribution are not established. A between-browse diagnostic is drafted but has not been built or run against either base or this head.7700c759and again atc779de3d. The matched cases slowed across several fixtures compared with master; the data does not establish whether the PR caused the slowdown. See the earlier CI run atc779de3dfor its results.masterbranch.Hosted test commands and results
These are the runner's
dotnet testarguments, with quotes added around arguments containing spaces or shell operators. The commands use hosted runner paths and are not local-solution runs.Windows net10.0 isolated PackMLObjects diagnostic on
32ff1f21, one failed:Windows net48 PackML, 220 passed:
Windows net10.0 PackML, 220 passed:
Linux net10.0 PackML, 220 passed:
macOS net10.0 PackML, 220 passed:
Windows net48 Server node management, 11 failed, 3,104 passed, one skipped:
Windows net48 Server node lifecycle, two failed, 467 passed:
Windows net10.0 Server, 13 failed, 10,985 passed, six skipped:
Linux net10.0 Server, 13 failed, 10,863 passed, 10 skipped:
macOS net10.0 Server node management, 11 failed, 3,104 passed, one skipped:
macOS net10.0 Server node lifecycle, two failed, 467 passed:
Linux net10.0 WoT sample-core: first attempt stopped after 64 passed; same-head retry passed 72:
Related Issues
Checklist