WoT Binding and WoT Connectivity 1.1 - #4093
Conversation
…ode-manager-lifecycle # Conflicts: # src/Opc.Ua.Server/NodeManager/AsyncCustomNodeManager.cs
…ode-manager-lifecycle # Conflicts: # src/Opc.Ua.Server/NodeManager/MasterNodeManager.cs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 29378426-2a98-41ee-9a6d-050af5b1141f
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 29378426-2a98-41ee-9a6d-050af5b1141f
…ver limits - Make uav:nodeSet.sha256 mandatory: a missing, malformed, or mismatched digest now produces a structured InvalidDigest/ DigestMismatch diagnostic and no NodeSet is returned, instead of silently skipping integrity verification when the field was absent. - Catch XML/deserialization failures from UANodeSet.Read and report them as a WotDiagnosticCode.MalformedNodeSet diagnostic instead of letting the exception escape the converter. - Create exactly one WotResolutionContext per top-level conversion in WotNodeSetConverter.ToNodeSetCore, seeded from the converter options (depth, max documents, cumulative bytes), and thread that single context through link/thing resolution. Removes the previous per-link context ??= new WotResolutionContext() fallback in TryResolveTargetNodeId, which reset cycle/limit tracking for every link instead of applying bounds across the whole conversion. - Seed WotNodeSetDocumentConverter's resolution context from its configured options instead of resolver defaults. - Add WotNodeSetConverterOptions.MaxResolverDocuments/ MaxResolverDocumentBytes/MaxResolverTotalBytes (aligned with WotResolverOptions defaults) plus ToResolverOptions() and validation, so aggregate resolver limits are configurable through the same options object used for the rest of the conversion. - Add tests: missing/malformed digest, digest mismatch, malformed NodeSet XML without throwing, unsupported vs. accepted (base64) encoding, multi-link aggregate document/byte limits sharing one context, and sibling-link cycle detection that doesn't block unrelated links. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
After a ShadowReload the retired generation is removed from the routing table but still owns the monitored items created on it. Modify, Delete, SetMonitoringMode and Transfer previously iterated only the visible routing-table managers, so a same-namespace replacement generation would claim (and fail on) the retired items - returning BadMonitoredItemIdInvalid and leaving the retired generation's items undrained and its address space never disposed until another lifecycle operation or shutdown. Dispatch each data monitored item to its recorded owning NodeManager (grouped by owner, each owner offered only its own indices) so a retired generation services its own items and a same-namespace replacement cannot cross-claim them. Event/all-event handling and result indexing are unchanged. Trigger prompt retired-generation cleanup when the last item drains: the master notifies the lifecycle from the Delete path when an owner is no longer registered, and the lifecycle runs cleanup on a background task (request execution context suppressed, coordinated via the lifecycle semaphore) so nothing is disposed inside the request callback. Existing fail-closed Reload/Remove behaviour and the deferred-cleanup fallback are preserved. Add tests proving that after ShadowReload old data items can be modified, disabled/re-enabled, transferred and deleted on the retired generation, new reads use the replacement, old notifications keep flowing, and the retired generation is disposed after the final drain without another lifecycle operation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rects) MQTT: mqtts now enables TLS and defaults to port 8883, resolving username / password, TLS client certificate and trust anchors through the credential provider. A declared security scheme fails closed when unresolved, and username / password never downgrades to plaintext unless explicitly allowed. plain mqtt stays explicit (port 1883). Modbus: the planner enforces address 0-65535 and the address+quantity-1 range, maps function-only forms exactly onto codes 1,2,3,4,5,6,15,16 (mnemonic or numeric) and rejects op/function and entity/function mismatches. The executor re-validates the range before the ushort/byte casts. HTTP: the executor-owned HttpClient disables automatic redirects and applies a bounded, origin-aware redirect policy that strips custom header/query credentials across origins, refuses loops, non-http(s) schemes and https->http downgrades, and caps hops. A caller-supplied client with a credential-bearing form fails closed unless CallerClientHandlesRedirectSafety is set. Adds planner, executor and E2E regression tests and updates the binding docs and options. WotCredential gains runtime-only client certificate / trust material. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Registry mutations now persist through a transactional store commit before publishing the snapshot or raising Changed, and the generated WoT V2 event types are fully populated from the coordinator arguments. - IWotRegistryStore: replace the per-entity Upsert/Remove methods with a single atomic CommitAsync(snapshot). LoadAsync returns only the committed generation. - FileWotRegistryStore: stage content-addressed version blobs durably, then switch a single manifest.json pointer with an atomic replace, so a crash never exposes a half-written generation; Load reads only the committed manifest. - InMemoryWotRegistryStore: keep the committed snapshot behind an atomic reference switch (same failure semantics). - WotRegistryService: commit durably before publishing m_snapshot or raising Changed for every CRUD/default/enabled/labels and projection path. On failure Current stays previous, no event is raised, a retry re-attempts persistence and a restart never sees partial data. - WotRegistryNodeManager: populate every generated WoT event field (resource identity/kind, generation, phase/outcome, validation outcome, load state/failed node/reason, binding uri, refresh summary/request id) from WotMaterializationEventArgs via SetChildValue. - Tests: fault-injection transaction tests (atomicity, no-event, retry, restart) and real EventFilter integration tests that verify the typed fields are delivered through the running server notifier chain. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Regenerate the Opc.Ua.WotCon model from the single revised Opc.Ua.WoTCon NodeSet2, which incorporates the published OPC 10100-1 v1.02 model (NodeIds 1..172, deprecated) plus the additive registry nodes (64000+) in one namespace (http://opcfoundation.org/UA/WoT-Con/, version 1.1.0), replacing the separate 1.02 ModelDesign and the Opc.Ua.WotCon.V2 companion model. Model / build: - Pin Opc.Ua.WoTCon.NodeSet2.xml/.csv (+ xRegistry) and drop the V2-named artifacts; update Sync-WotConModels.ps1 mappings/docs. - Opc.Ua.WotCon.csproj: stop compiling WotConnection.xml/csv; compile the combined NodeSet once as Opc.Ua.WotCon (name WotCon, model URI WoT-Con/). Legacy 1.02 sources kept under Design/ for reference only. Server / tests / docs: - Replace all Opc.Ua.WotCon.V2 namespace/type references with the combined Opc.Ua.WotCon generated types (AddOpcUaWotCon, ObjectIds, BrowseNames, DataTypeIds, ...); remove user-visible V2 wording and rename the registry config section to OpcUa:WotConRegistry:Server. - Decide model-node ownership cleanly via WotConModelPartition: the registry NodeManager owns {WotCon, xRegistry} and loads the registry slice (>=64000); the legacy asset NodeManager loads the incorporated 1.02 slice (<64000). Neither claims the same static node twice. - Add CombinedModelPreservationTests proving exact 1.02 NodeIds, typed method state/results and the generated client API are preserved, and that the registry types now coexist in Opc.Ua.WotCon. Source generator (NodeSet2 -> ModelDesign) fixes exposed by generating the richer combined model with typed method arguments: - Map placeholder BrowseNames "<Name>" to "Name_Placeholder". - Reuse an existing explicit method-type declaration instead of synthesizing a colliding duplicate, and carry the concrete method's arguments onto it. - Register the standard encodeable types (Argument) on the value decoder so NodeSet2 method argument lists actually decode (was silently empty). - Disambiguate a by-ref output argument whose name matches an input. - Skip synthesizing method types for base-namespace (inherited) methods and for standalone method-type declarations. Deprecated 1.02 nodes remain usable and all 1.02 tests pass unchanged (only mechanical Ua.DataTypeIds qualification where the new combined DataTypeIds shadows Core). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Superseded by the 14-PR stack #4159 plus three follow-up draft PRs. Closing after a line-level audit confirmed full coverage. How this was verifiedA raw diff between this branch and the stack tip is misleading — they have different merge-bases with
Starting gap: 742 lines across 84 files (~99.3 % already covered). After the three follow-up PRs: 303 lines, all classified below. Where the content went
Residual differences — all intentionalThe remaining 303 lines are places where the stack is newer or better, plus deliberate skips:
Fixes carried acrossTwo improvements from this PR that the split had dropped were deliberately preserved rather than lost:
Validated on a tree with all three follow-up PRs merged: |
…n context (#4163) # Description `RequestManager` tracked whether the calling flow was serving a Client request in a `private readonly AsyncLocal<bool> m_inServiceDispatch`. It was set by a `StandardServer.ProcessRequestAsync` override and read by `NodeManagerLifecycle`, so that a lifecycle operation (`AddAsync` / `ReloadAsync` / `RemoveAsync`) started from inside a request fails fast with `InvalidOperationException` instead of draining — and therefore waiting for — its own request. Two problems with that mechanism, raised in review of #4093: 1. An `AsyncLocal<T>` written inside an `async` method never flows back to the caller that awaited it, so every scope had to chain to the previous value by hand and restore it on dispose. Subtle, and easy to get wrong when a new entry point is added. 2. Ambient state is invisible at the call site, so the lifetime of the mark could not be reasoned about from the code that depended on it — especially once work is handed to a background task. `RequestManager` also had no business carrying ambient state on behalf of a different subsystem; its job is tracking requests for the drain. ## What changed The guard is now an **exact identity lookup** against the request registry `RequestManager` already maintains, and the operation is threaded **explicitly**: * **`RequestManager`** — `internal bool IsExecutingRequest(IOperationContext?)` performs a reference-identity lookup against `m_requests` under the existing `m_requestsLock`. `m_inServiceDispatch`, `EnterServiceDispatchScope()`, the nested `ServiceDispatchScope` class and the flag-based `IsExecutingRequest` property are gone. * **`StandardServer`** — the `ProcessRequestAsync` override is removed; it existed only to open the dispatch scope. * **`INodeManagerLifecycle`** — every member (`AddAsync` ×2 factory kinds, `ReloadAsync` ×2, `RemoveAsync`) now takes the caller's `IOperationContext?` directly, ahead of `CancellationToken ct = default`. No overloads and no parallel signature are kept: the interface is new in 2.0 and outside the 1.5.378 compatibility rule, so call sites state their context explicitly or pass `null`. * **`HostedNodeManagerLifecycle`** and **`RuntimeNodeSetLifecycleExtensions`** thread the argument through; `AddRuntimeNodeSetAsync` / `ReloadRuntimeNodeSetAsync` remain single methods. * **`SystemContextOperationExtensions.GetOperationContext(this ISystemContext)`** (new, `Opc.Ua.Core`) lets a NodeManager or Method callback hand over the operation it already received without downcasting. It switches on `SessionSystemContext` and `SystemContext`, the two independent roots every context derives from. No new interface is introduced: casting the context to `IOperationContext` would not do, because a context implements that by delegating to the operation it was created for, so the cast yields the context rather than the operation. * **`NodeManagerLifecycle.EnsureNotRequestCallback`** no longer consults `ServerState`. The previous `CurrentState == Running` predicate could let a genuinely re-entrant call slip past while the server was shutting down; the request registry answers exactly in every state. A NodeManager or Method callback now reads: ```csharp await m_lifecycle.ReloadAsync(m_registration, replacement, context.GetOperationContext(), ct); ``` and a control-plane caller (hosted service, DI consumer) passes `null`. ## Behaviour Unchanged for the case the guard exists to catch: a lifecycle call made on behalf of an executing request is still rejected up front with the same `InvalidOperationException` and the same message. Two deliberate differences: * **Improvement** — an internal `OperationContext` that was never enrolled as a Client request is now correctly allowed through. The boolean flag could not distinguish it from a real request. * **Accepted trade-off** — a caller inside a request that passes no operation is no longer detected at entry and falls back to the pre-existing bounded `RequestManager.RequestDrainTimeout`, which already covered any request that bypassed the service pipeline. Automatic detection for an uncooperative caller inherently requires ambient state, which is exactly what this change removes. Documented in `docs/NodeManagers.md`. ## Alternatives considered and rejected | Alternative | Why rejected | | --- | --- | | Delete the guard, rely only on `RequestDrainTimeout` | Turns a clear immediate error into a multi-minute hang. | | Infer the caller from the `CancellationToken` it passes | Works only for the exact request token, breaks on linked tokens; ambient magic of a different kind. | | In-flight callback counter on `MasterNodeManager` | False positives: rejects legitimate control-plane calls whenever any request is concurrently dispatching. | | Defer the drain to a background task so re-entrancy cannot deadlock | The drains are woven into transactional commit / rollback and binding reconciliation; deferring them breaks the transactional guarantees. | | `Activity.Current` / `ThreadLocal` | The same ambient magic the issue asks to remove. | ## Testing * `RequestManagerTests` — the three `EnterServiceDispatchScope` tests are replaced by coverage for `IsExecutingRequest(context)`: null, never-registered, only-while-scope-open, visible across an `await` and a background `Task.Run`, and two concurrently executing requests not confusing each other. * `NodeManagerLifecycleTests` — rejection from an executing request for Add / Reload / Remove without invoking the factory, acceptance for a context that is not an executing request, acceptance once the request has completed, and the full callback shape (`ServerSystemContext.Copy(operation)` → `GetOperationContext()` → rejected). * `HostedNodeManagerLifecycleTests` — forwarding tests for the caller-context argument on all five members. * `tests/Opc.Ua.Core.Tests/Stack/State/SystemContextOperationExtensionsTests.cs` (new) — `GetOperationContext` across both context roots and the null-argument guard. ## Related Issues - Fixes #4149 ## 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. - [x] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 14402d11-e540-4faa-a5cd-94881bd7ce85
…ion (#4156) Adds the runnable WoT Connectivity samples and completes the documentation. This is **PR 16 of a stack** splitting #4093, and the last one. ## Samples Three projects under `samples/WotCon/`: * **FlatTagServer** — a plain OPC UA server exposing flat tags, used as an aggregation source. * **AggregationServer** — registers Thing Descriptions with the WoT registry and materializes two flat sources into a runtime-loaded DI/Machinery/Pumps Pump model. It also ships a small in-memory protocol binding, which is the worked example the binding contributor guide walks through. * **AggregationClient** — drives the aggregated model: commands, `Refresh`, monitoring, and generation replacement. ## Documentation * New `docs/WotBindings.md` — the bindings that ship today (planner/executor architecture, bundled vs separate packages, operation coverage, target mapping, lazy channels, generation lifetime) plus the contributor guide for adding your own, with diagnostics, tests, packaging, TFM, trimming and NativeAOT guidance. * Expanded `docs/WoTConnectivity.md` covering the registry, dependency-closure materialization and runtime NodeSet projection. * `docs/RuntimeNodeSets.md` gains the shadow-reload section and `docs/XRegistry.md` the shared byte-store section. Both document APIs introduced earlier in the stack; they are collected here because this is the documentation PR. * `docs/README.md` links the new pages. ## Solution registration Registers the three samples and `Opc.Ua.WotCon.Samples.Tests` in `UA.slnx` so CI actually builds and runs them. ## Stack position Sits on top of **#4154** (materialization) and additionally merges **#4142** (registry client) and **#4144** (protocol executors), because the samples exercise the whole stack — `AggregationServer` needs the OPC UA executor and the client sample needs the registry client. Its diff therefore shows their content until they land. It also carries `WotRegistryProjectionLiveTests.cs`, which #4154 deliberately left out: those tests need both the materialization runtime *and* the registry client, so this is the first branch where they compile. ## Validation * All three samples build clean on every target framework. * `Opc.Ua.WotCon.Tests`: **830 passed**, 0 failed (769 from #4154 plus 61 from the live projection tests). * `Opc.Ua.WotCon.Samples.Tests`: 6 of 7 pass locally; the remaining one fails with `CryptographicException: The system cannot find the path specified`, a known certificate-store problem on this machine that was previously reproduced against a clean baseline worktree and is not related to this change. CI will confirm. * 0 warnings, 0 errors. One fix was needed while bringing the sample across: its memory binding called `ResolveCodec` with the pre-`out WotPayloadDescriptor` signature. The API gained the payload out-parameter and a `bool` result during review of the binding PRs, so the sample now follows the same `if (!ResolveCodec(...)) return Unsupported(...)` pattern as the shipped planners. Integration PR: #4093.
…pace (#4154) PR 14 of the stacked series splitting the large WoT Connectivity integration PR #4093. This PR adds the server-side materialization runtime that turns WoT Thing Description resources from the xRegistry snapshot into OPC UA address-space projections. The coordinator builds the dependency closure for each resource, plans binding metadata, activates binding channels, and publishes the resulting projection through the registry NodeManager so refresh, retire, and reload operations remain observable and deterministic. Projection intentionally goes through the NodeManager lifecycle reload API instead of mutating live nodes directly. That keeps complex type reload, runtime NodeSet publication, shadow reload, immediate reload, and retirement on the same lifecycle path as other dynamic server address-space changes. This branch has a triple dependency. It is based on #4146 (registry), and additionally needs #4147 (lifecycle reload API) and #4128 (2-argument TryGetStructure). Both #4147 and #4128 are merged into this branch, so this PR's diff will show their content until those dependencies land. Validation performed: - dotnet build src\Opc.Ua.WotCon.Server\Opc.Ua.WotCon.Server.csproj -c Release -f net10.0 -v:m - dotnet build tests\Opc.Ua.WotCon.Tests\Opc.Ua.WotCon.Tests.csproj -c Release -p:CustomTestTarget=net10.0 -v:m - dotnet test tests\Opc.Ua.WotCon.Tests\Opc.Ua.WotCon.Tests.csproj -c Release -p:CustomTestTarget=net10.0 --no-build - dotnet build tests\Opc.Ua.WotCon.Tests\Opc.Ua.WotCon.Tests.csproj -c Release -p:CustomTestTarget=net48 -v:m - dotnet test tests\Opc.Ua.WotCon.Tests\Opc.Ua.WotCon.Tests.csproj -c Release -p:CustomTestTarget=net48 --no-build - dotnet build tests\Opc.Ua.Server.Tests\Opc.Ua.Server.Tests.csproj -c Release -p:CustomTestTarget=net10.0 -v:m - dotnet test tests\Opc.Ua.Server.Tests\Opc.Ua.Server.Tests.csproj -c Release -p:CustomTestTarget=net10.0 --no-build (one known inherited failure: LiveNodeManagerAddRefreshesTheClientNamespaceTable)
PR 15 of a stacked split of integration PR #4093 into independently reviewable pieces. This branch is stacked on #4139 (`marcschier/wot-10-wotcon-model`) and additionally has #4128 (`marcschier/wot-01-core-types`) and #4130 (`marcschier/wot-09-xregistry-client`) merged in so this client slice can build. Those extra dependencies are intentional: - Without #4130: `WotRegistryClient.cs:83` — `error CS1729: 'XRegistryClient' does not contain a constructor that takes 4 arguments`. - Without #4128: `WotRegistryClient.cs:346,375` and `WotRegistryResourceClient.cs:144` — `error CS1501: No overload for method 'TryGetStructure' takes 2 arguments`. The extra diff from #4128 and #4130 disappears once those PRs land. This PR adds the WoT Connectivity registry client surface for browsing and reading registry groups, resources and versions, uploading documents through FileTransfer, invoking registry methods, and registering the client through DI/builder helpers.
Adds the WoT Connectivity registry service and stores for Thing Description and Thing Model documents. This is PR 13 of the stacked split of integration PR #4093 and is stacked on #4140 (`marcschier/wot-11-bindings-core`). The materialization runtime that consumes this registry follows separately. The registry has no dependency on the binding runtime or the NodeManager lifecycle; the stacking is only because this change shares the WoT Connectivity server project file with the preceding PRs. Validation: - `dotnet build src\Opc.Ua.WotCon.Server\Opc.Ua.WotCon.Server.csproj -c Release -f net10.0 -v:m` - `dotnet build tests\Opc.Ua.WotCon.Tests\Opc.Ua.WotCon.Tests.csproj -c Release -p:CustomTestTarget=net10.0 -v:m` - `dotnet test tests\Opc.Ua.WotCon.Tests\Opc.Ua.WotCon.Tests.csproj -c Release -p:CustomTestTarget=net10.0 --no-build -v:q` - `dotnet build src\Opc.Ua.WotCon.Server\Opc.Ua.WotCon.Server.csproj -c Release -v:m`
## Summary - Adds the concrete WoT binding transport executors for HTTP, Modbus TCP, OPC UA and MQTT. - Includes executor/channel tests and in-process HTTP, Modbus, MQTT and OPC UA harness coverage. - Leaves CoAP, BACnet, PROFINET and LoRaWAN planner-only because this stack split intentionally lands validation/planning before executable transports. ## Stack context This is PR 12 of the stacked split of integration PR #4093. It is stacked on #4140, where the protocol-agnostic planner layer landed separately, so this PR is limited to the concrete transport implementations. ## Validation - `dotnet build tests\Opc.Ua.WotCon.Bindings.Tests\Opc.Ua.WotCon.Bindings.Tests.csproj -c Release -p:CustomTestTarget=net10.0 -v:m` - `dotnet test tests\Opc.Ua.WotCon.Bindings.Tests\Opc.Ua.WotCon.Bindings.Tests.csproj -c Release -p:CustomTestTarget=net10.0 --no-build` (512 passed) - `dotnet build src\Opc.Ua.WotCon.Bindings\Opc.Ua.WotCon.Bindings.csproj -c Release -v:m` - `dotnet build src\Opc.Ua.WotCon.Bindings.Mqtt\Opc.Ua.WotCon.Bindings.Mqtt.csproj -c Release -v:m`
## Summary PR 11 of the stacked split for integration PR #4093. This adds the protocol-agnostic core of `Opc.Ua.WotCon.Bindings`: binder, planner, executor and channel-factory contracts; payload codec and credential provider seams; the binding plan model; polling subscriptions for forms without native observation; and DI/builder registration helpers. This PR is stacked on #4139, which itself depends on #4132 and #4134. ## Scope The included planners validate forms and produce binding plans without transport I/O: - HTTP planner and validation only; the concrete HTTP executor follows in the next PR. - MQTT planner and validation only; the separate MQTT executor package follows in the next PR. - Modbus TCP planner and shared Modbus type/limit definitions only; the Modbus executor and TCP client follow in the next PR. - OPC UA planner and validation only; the concrete OPC UA executor follows in the next PR. - CoAP, BACnet, PROFINET and LoRaWAN are planner/validation-only in this split. Keeping concrete transport executors out of this PR makes the abstractions and planning layer reviewable without a network stack. ## Validation - `dotnet build tests\Opc.Ua.WotCon.Bindings.Tests\Opc.Ua.WotCon.Bindings.Tests.csproj -c Release -p:CustomTestTarget=net10.0 -v:m` - `dotnet test tests\Opc.Ua.WotCon.Bindings.Tests\Opc.Ua.WotCon.Bindings.Tests.csproj -c Release -p:CustomTestTarget=net10.0 --no-build` - `dotnet build src\Opc.Ua.WotCon.Bindings\Opc.Ua.WotCon.Bindings.csproj -c Release -v:m`
This is PR 10 of the stacked split of integration PR #4093 into independently reviewable pieces. Stack/dependency shape: - This branch is stacked on #4134 (`marcschier/wot-04-generator-wot`). - It also requires #4132 (`marcschier/wot-02-sourcegen`), which is merged into this branch so the model can build. The #4132 dependency is functional, not cosmetic. Without #4132, building the WoT Connectivity model on #4134 alone fails with duplicate generated declarations, for example: ```text CS0102: The type 'Methods' already contains a definition for 'CreateAssetMethodType' ``` That occurs because the combined WoT-Con NodeSet ships the 1.02 `CreateAssetMethodType` node explicitly; without #4132's NodeSet-to-ModelDesign fix, the generator synthesizes a second declaration with the same name. Once #4132 and #4134 land, the extra diff from those dependencies disappears and this PR reduces to the three WoT Connectivity model files: - `src\Opc.Ua.WotCon\Design\Opc.Ua.WotCon.NodeSet2.xml` - `src\Opc.Ua.WotCon\Design\Opc.Ua.WotCon.NodeSet2.csv` - `src\Opc.Ua.WotCon\Opc.Ua.WotCon.csproj` The generated C# is produced from the pinned NodeSet2 at build time. I verified it builds from clean after deleting the WotCon `bin`/`obj`, the source-generation tool `bin`/`obj` directories, and shutting down dotnet build servers.
This extracts the xRegistry client generalisation from #4093 as PR 9 of the stacked split into independently reviewable pieces. The change lets the xRegistry client base accept an explicit registry root NodeId while preserving the existing well-known-root default. GenericXRegistryClient exposes the same explicit-root path so callers can drive registries whose root Object is discovered separately, and the tests cover the supplied-root and fallback behavior across the lifecycle helpers. This PR is independent of the other stack PRs and contains no WoT registry implementation or Opc.Ua.WotCon.* references. A later PR adds the WoT registry client on top of this general xRegistry client surface.
…4150) This is PR 7 of the stacked series splitting integration PR #4093, stacked on #4147 (`marcschier/wot-05-lifecycle`). This PR makes subscription transfer between sessions transactional: subscriptions are prepared before any ownership move, publish-queue transfer claims block stale source-session publishing, and rollback restores monitored-item resend-data trigger state. `ServerInternalData` also gains asynchronous disposal so the asynchronously disposable subscription manager is shut down without blocking, with sync and async dispose paths guarded so repeated disposal is a no-op. It also brings the transfer-focused coverage for subscription transfer, publish-queue transfer claims, and `ServerInternalData` async/double-disposal behavior. The immediate monitored-item retirement mechanism present in #4093 is deliberately excluded per maintainer decision. `IRetirableMonitoredItem` and `INodeManagerMonitoredItemRetirementTracker` do not appear in this PR.
## Summary Adds PR 5 of the stack splitting #4093 into independently reviewable pieces, targeting `master` directly. This PR extends the NodeManager lifecycle with live shadow reload support and RuntimeNodeSet lifecycle helpers. `ShadowReloadAsync` stages a replacement generation and routes new service requests to it while monitored items already owned by the retired generation continue there until subscriptions drain. `ImmediateReloadAsync` keeps the immediate policy but implements it on the existing detach machinery at the maintainer's request: affected non-durable monitored items are detached/marked deleted with `IDetachableMonitoredItem`, queue `BadNodeIdUnknown`, and are not migrated to the replacement generation. The prior NodeManager generation is disposed before `ImmediateReloadAsync` returns in the covered lifecycle path. The bespoke permanent retirement-error path is intentionally removed, so follow-up operations use the existing detached-item semantics rather than a separate retirement state. ## Validation - `dotnet build src\Opc.Ua.Server\Opc.Ua.Server.csproj -c Release -f net10.0 -v:m` - `dotnet build tests\Opc.Ua.Server.Tests\Opc.Ua.Server.Tests.csproj -c Release -p:CustomTestTarget=net10.0 -v:m` - `dotnet test tests\Opc.Ua.Server.Tests\Opc.Ua.Server.Tests.csproj -c Release -p:CustomTestTarget=net10.0 --no-build` — 3914 passed, 9 skipped, 0 failed - `dotnet build tests\Opc.Ua.Server.Tests\Opc.Ua.Server.Tests.csproj -c Release -p:CustomTestTarget=net48 -v:m` - `dotnet test tests\Opc.Ua.Server.Tests\Opc.Ua.Server.Tests.csproj -c Release -p:CustomTestTarget=net48 --no-build` — full run hit local durable queue file-lock flake; the failed test passed in isolation. Full run otherwise reported 3913 passed, 9 skipped, 1 failed.
…files (#4134) ## Summary - Adds WoT Thing Description and Thing Model AdditionalFiles support to Opc.Ua.SourceGeneration for .tm.json, .td.json, .tm.jsonld, .td.jsonld, and explicitly opted-in .jsonld inputs. - Converts WoT inputs to an in-memory UANodeSet and reuses the existing NodeSet generation pipeline for constants, node states, and proxies. - Reports parsing, conversion, and virtual path collision failures as MODELGEN030 through MODELGEN034 diagnostics instead of generator exceptions. ## Stack context This is PR 4 of the stacked split of integration PR #4093. It is stacked on #4131 (`marcschier/wot-03-converter`) and should be reviewed and merged after that PR. The source-generation entry-point project is included whole in this PR because its final state is irreducibly WoT-coupled. The genuinely generic generator work lives in the separate PR 2 branch. ## Notes Roslyn source generators must run synchronously. This PR drives WoT conversion through the converter's synchronous entry point and does not use the asynchronous resolver contracts from generator code. ## Validation - `dotnet build tools\Opc.Ua.SourceGeneration\Opc.Ua.SourceGeneration.csproj -c Release -v:m` - `dotnet build tests\Opc.Ua.SourceGeneration.Tests\Opc.Ua.SourceGeneration.Tests.csproj -c Release -p:CustomTestTarget=net10.0 -v:m` - `dotnet test tests\Opc.Ua.SourceGeneration.Tests\Opc.Ua.SourceGeneration.Tests.csproj -c Release -p:CustomTestTarget=net10.0 --no-build` - `dotnet build tests\Opc.Ua.SourceGeneration.Tests\Opc.Ua.SourceGeneration.Tests.csproj -c Release -p:CustomTestTarget=net48 -v:m` - `dotnet test tests\Opc.Ua.SourceGeneration.Tests\Opc.Ua.SourceGeneration.Tests.csproj -c Release -p:CustomTestTarget=net48 --no-build`
…rated node state (#4132) ## Summary - Improves NodeSet to ModelDesign conversion for combined and multi-namespace NodeSets. - Fixes generated method declarations, argument resolution, object type proxies, fluent builders, generated node state, shared generation helpers, and validation. - Regenerates ISA95 NodeIds as generator output from these changes, not as a hand edit. ## Stack context This is PR 2 of a stacked split of integration PR #4093 into independently reviewable pieces. This PR is independent of the other stack PRs and contains no WoT content. ## Validation - `dotnet build tests\Opc.Ua.SourceGeneration.Core.Tests\Opc.Ua.SourceGeneration.Core.Tests.csproj -c Release -p:CustomTestTarget=net10.0 -v:m` - `dotnet test tests\Opc.Ua.SourceGeneration.Core.Tests\Opc.Ua.SourceGeneration.Core.Tests.csproj -c Release -p:CustomTestTarget=net10.0 --no-build` - `dotnet build tests\Opc.Ua.SourceGeneration.Core.Tests\Opc.Ua.SourceGeneration.Core.Tests.csproj -c Release -p:CustomTestTarget=net48 -v:m` - `dotnet test tests\Opc.Ua.SourceGeneration.Core.Tests\Opc.Ua.SourceGeneration.Core.Tests.csproj -c Release -p:CustomTestTarget=net48 --no-build` - `dotnet build src\Opc.Ua.ISA95\Opc.Ua.ISA95.csproj -c Release -f net10.0 -v:m` - Confirmed ISA95 build leaves `src\Opc.Ua.ISA95\Design\Common\Opc.ISA95.NodeIds.csv` with no unstaged regeneration diff.
…4131) Adds a dependency-light, AOT-safe lexical model for Thing Description and Thing Model documents to Opc.Ua.Types, together with lossless converters between WoT documents and NodeSet2 and canonical NodeSet comparison for round-trip checks. This is PR 3 of a stacked split of #4093 into independently reviewable pieces. It is self-contained, targets master directly, and depends on no other PR in the stack. The change is a pure library in Opc.Ua.Types with no server/client involvement and no network I/O of its own. Unknown JSON-LD members are preserved as digest-protected RFC 6901 residue entries, native NodeSet projection is emitted according to the preservation mode, and resolver contracts expose asynchronous bounded resolution with structured diagnostics. This PR is still large (~8,000 lines). Suggested review passes: 1. Lexical model and residue preservation. 2. Converter and native projection. 3. Resolver contracts and NodeSet comparison.
…ation, Server and xRegistry work it builds on (#4128) ## Summary Adds OPC UA WoT Connectivity 1.1 to the stack, together with the Types, source generation, Server and xRegistry work it is built on. This branch is the merge point for the fourteen-PR stack that replaced integration PR #4093; every constituent PR was reviewed and approved separately and is listed below. 414 files changed, +108,936 / -3,199. Roughly 46k added lines of product code, 57k of tests, plus samples, tools and documentation. ## Types - Complete `ExtensionObject` raw-body decoding for binary, XML and JSON bodies by resolving the concrete type through the message context's encodeable factory, and simplify the `Variant` structure helpers by delegating to it (#4128). - Add `IAtomicFileReplace` as an optional `IFileSystem` capability so existing external implementations keep working, with atomic publish for `LocalFileSystem` via `File.Replace`/`File.Move` and for `VirtualFileSystem` by re-keying the in-memory entry (#4128). - Add lossless conversion between WoT documents and NodeSet2 (`Opc.Ua.Wot.WotNodeSetConverter`). A byte-exact `uav:nodeSet` envelope is preserved when requested, the structured `uav:nodes` projection is used when the readable vocabulary is incomplete, and NodeSet2 is otherwise synthesized from readable WoT terms. Unmapped JSON members survive a round trip as pointer-addressed residue (#4131). ## Source generation - Improve the NodeSet to ModelDesign conversion and the generated node state (#4132). - Generate OPC UA models directly from WoT Thing Description files (#4134). ## Server - Add live NodeManager shadow and immediate reload. A reload materializes a new generation beside the active one and switches atomically; the superseded generation is retired gracefully so existing MonitoredItems keep being served until they drain, or immediately when the caller asks for it. Includes the request admission and drain machinery that lets an orderly shutdown wait for admitted requests instead of tearing down underneath them (#4147). - Make subscription transfer between sessions transactional, so a failed transfer leaves neither session holding a partially moved subscription (#4150). ## xRegistry - Allow clients to use explicit registry roots rather than assuming a well-known location (#4130). ## WoT Connectivity - Add the WoT Connectivity 1.1 information model: a registry-first revision layered on the abstract xRegistry base model, incorporating the complete published OPC 10100-1 v1.02 surface as deprecated nodes in the same namespace so existing 1.02 clients keep working (#4139). - Add the protocol binding abstractions and planners that compile WoT forms into executable plans (#4140). - Add the HTTP, Modbus, OPC UA and MQTT binding executors (#4144). - Add the registry and its stores, including document versioning, validation, dependency resolution and refresh (#4146). - Add the registry client (#4142). - Materialize WoT Thing Descriptions into the server address space, deriving types from Thing Models and instances from Thing Descriptions (#4154). - Restore the WoT asset registry bridge so the deprecated 1.02 asset surface is backed by the registry (#4171). - Add the WoT Connectivity samples and complete the documentation (#4156), and restore the remaining coverage and docs (#4172). ## New projects `src/Opc.Ua.WotCon.Bindings`, `src/Opc.Ua.WotCon.Bindings.Mqtt`, `tests/Opc.Ua.WotCon.Bindings.Tests`, `tests/Opc.Ua.WotCon.Samples.Tests`, and the `samples/WotCon` AggregationServer, AggregationClient and FlatTagServer samples. ## Constituent pull requests Merged into this branch top-down, each independently approved: | PR | Title | | --- | --- | | #4128 | [Types] Complete ExtensionObject decoding and add an atomic file replace capability | | #4131 | [Types] Add lossless conversion between WoT documents and NodeSet2 | | #4132 | [SourceGeneration] Improve NodeSet to ModelDesign conversion and generated node state | | #4134 | [SourceGeneration] Generate OPC UA models from WoT Thing Description files | | #4147 | [Server] Add live NodeManager shadow and immediate reload | | #4150 | [Server] Make subscription transfer between sessions transactional | | #4130 | [XRegistry] Allow clients to use explicit registry roots | | #4139 | [WotCon] Add the WoT Connectivity 1.1 information model | | #4140 | [WotCon] Add the WoT protocol binding abstractions and planners | | #4144 | [WotCon] Add the HTTP, Modbus, OPC UA and MQTT binding executors | | #4146 | [WotCon] Add the WoT Connectivity registry and its stores | | #4142 | [WotCon] Add the WoT Connectivity registry client | | #4154 | [WotCon] Materialize WoT Thing Descriptions into the server address space | | #4156 | [WotCon] Add the WoT Connectivity samples and complete the documentation | | #4171 | Restore WoT asset registry bridge | | #4172 | Restore WoT coverage and docs | Supersedes #4093. ## Follow-up The WoT Connectivity and WoT Binding drafts have moved to 1.1-draft2 since this work was authored. Aligning with that revision - the removed group vocabulary, the new projection/View construct, the remaining model vocabulary terms and the two new portable-identity validation rules - is tracked separately and will follow in its own pull request. ## Validation Built for every target framework with zero warnings and zero errors, and validated at the tip of the stack with `Opc.Ua.WotCon.Tests` at 999 passed / 0 failed and `Opc.Ua.Server.Tests` at 4024 passed / 0 failed. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
Status and dependencies
Draft proof implementation.
OPCFoundation/UA-.NETStandard:marcschier/wot-binding-xregistry.[Server] Add live NodeManager lifecycle support) has landed, so this PR now targetsmasterdirectly and the lifecycle dependency is resolved.What this proves
Lossless NodeSet2 ↔ WoT conversion
Opc.Ua.Types.Extensions.uav:nodesprojection only when readable-only reverse conversion does not reproduce the complete UANodeSet.WhenRequired,Always, andNeverpreservation modes.uav:nodeSetis emitted only for explicit byte archival or a demonstrated fallback, and completeness tests useNever.WoT source generation
.tm.json,.td.json,.tm.jsonld,.td.jsonldand explicitly opted-in.jsonldAdditionalFiles.UANodeSetand uses the existing direct NodeSet code-generation pipeline.MODELGEN030–MODELGEN034diagnostics for parsing, conversion and virtual-path collisions.Live NodeManager shadow reload
ShadowReloadAsync.ImmediateReloadAsyncas the alternative policy: it atomically routes new requests to the replacement, invalidates affected non-durable monitored items withBadNodeIdUnknown, detaches disposed owner graphs, and rejects durable subscriptions before commit.ReloadAsync/RemoveAsyncbehavior unchanged.Registry-first WoT Connectivity 1.1
WotProjectionRetirementPolicyso deployments select graceful or immediate replacement.Replaceable protocol binding code-behind
Adds a dependency-light planner/binder project for:
Concrete optional executor projects cover HTTP, MQTT, Modbus TCP and OPC UA. The OPC UA executor supports Read, Write, native data-change observation, Method invocation and Event subscription, including portable
nsu=NodeIds.BACnet, PROFINET, LoRaWAN and CoAP are planner/validation-only in this proof and report their non-executable capability explicitly.
Review remediation
The follow-up review fixes are included:
mqttsenables TLS, defaults to 8883, resolves credentials/trust and fails closed;uav:nodeSet.sha256is mandatory and malformed XML becomes structured diagnostics;uav:eventType, persisted identities use portablensu=ExpandedNodeIds, andHasComponentsubtypes round-trip exactly;uaand deterministicnsNprefixes; ReferenceType model names are emitted directly in linkrel, withuav:refIdas definitive identifier, and name/NodeId conflicts are rejected;mapToTypeName/congruentTypeNamehints remain paired with definitive identifiers, while arbitrary instance targets remain ExpandedNodeIds;uav:typedReference/refTypeNamedraft surface is removed;@contextprefixes (with URI-qualified QualifiedNames also accepted), authored IDs are retained during synthesis, and the proof omitsuav:nodeswhen the semantic mapping alone is equivalent;observable/observepropertyis documented as advertised binding support, not a restriction on core UA monitorability.Compatibility
IWotAssetProviderFactoryintegrations remain supported.Opc.Ua.WotCon.Server.Commit structure
Add shadow NodeManager reload supportAdd lossless WoT conversion and source generationAdd injectable WoT protocol bindingsAdd registry-first WoT Connectivity runtimeAlign WoT Connectivity proof to combined 1.1 spec modelAlign WoT converter with portable Binding identityMake WoT NodeSet conversion native-firstAddress WoT model concept review feedbackAddress WoT mapping review feedbackPrefer context prefixes for WoT QualifiedNamesImplement selectable WoT projection retirementConsolidate WoT bindings and add WoT-driven aggregation sampleApply code style and analyzer fixes to WoT bindings branchWire the DI NodeManager fluent builder DataType resolverAddress PR review feedback on WoT docs and sample layoutRaise PR patch coverage from 70% to 91% with unit and integration testsFix net48 test failures and remove dead WoT mapping codeMerge copilot/3993-live-node-manager-lifecycle into marcschier/wot-binding-xregistryAdopt the merged xRegistry implementation and packagesDerive WotRegistryClient from the shared XRegistryClientMake subscription transfer effects transactionalAdd a WoT blob store implementing the xRegistry resource store contractLet a WoT registry keep its document bytes in an injected resource storeAdd WoT materialization and polling extension points (#4108, #4109, #4110)Address code review findings and PR feedbackValidation
Opc.Ua.Types.Testsnet10.0 and net48: passedOpc.Ua.WotCon.TestsandOpc.Ua.WotCon.Bindings.Testsnet10.0: passeddotnet build UA.slnx -c Release: passedOpc.Ua.Server/Opc.Ua.WotCon.Serverbuilds on net10.0 and net472: passed with 0 warningsgit diff --check: cleanDocumentation layout
docs/WoTProtocolBindings.md+docs/WoTBindingDevelopment.mdare merged into a singledocs/WotBindings.md, ordered as Part 1 (the bindings that ship today) and Part 2 (adding your own binding).docs/WoTAggregationSample.mdmoved tosamples/WotCon/README.md, next to the WoT samples it documents.samples/WotCon/AggregationClient/Documents: the client is what uploads them into the server, and the aggregation server has no dependency on them.AutoRefresh = trueversus the sample'sAutoRefresh = false.Test coverage
Codecov reported 70.39 % patch coverage against the 80 %
codecov/patchgate. Tests were added acrossOpc.Ua.Types/Wot,Opc.Ua.WotCon.Server,Opc.Ua.WotCon.Bindings*,Opc.Ua.Serverand the WoT source generator, raising measured patch coverage to 91.2 % (10 750 of 11 782 changed lines). No production behaviour was changed to achieve it; the only production edit was removing the unreachableWotNodeSetConverter.ToCompactModelNamehelper.Suite totals on net10.0, all warning-free:
Opc.Ua.Types.Tests8 390,Opc.Ua.WotCon.Tests823,Opc.Ua.WotCon.Bindings.Tests464,Opc.Ua.SourceGeneration.Tests94,Opc.Ua.Server.Tests3 851. On net48:Opc.Ua.Types.Tests8 383 andOpc.Ua.WotCon.Tests823.Base branch merge
The live NodeManager lifecycle work (#4094) has landed in
masterand has been merged in; this PR is now based onmaster. The upstream NodeManager lifecycle interface consolidation was adopted (IMonitoredItemLifecycle→IDetachableMonitoredItem,INodeManagerMonitoredItemTrackerfolded intoISubscriptionMonitoredItemLifecycle,ExecuteMonitoredItemMutationAsyncmoved toIDynamicNodeManagerHost) while retaining this branch's shadow/immediate reload support;INodeManagerMonitoredItemRetirementTrackernow lives in its own file to match the new one-interface-per-file layout.Reported issues addressed
Raised by @barnstee while evaluating whether UA Edge Translator can replace its hand-written WoT-Connectivity node manager with this library. All four target the deprecated OPC 10100-1 v1.02 asset surface; the answers live in the registry-first materialization runtime and
Opc.Ua.WotCon.Bindingsthat this PR adds.uav:mapToNodeId/uav:mapToType/uav:mapByFieldPathper OPC 10101 §6.5.4, authored on the property affordance (never a form), withWotTargetMappingDescriptor,IWotTargetVariableResolverandWotStructuredFieldBinding.IWotNodeSetContributorruns per resource after conversion and before variable creation, forStructureTypes generated from a controller symbol table;IWotDocumentConverteris now also resolvable from DI.StatusCodepropagation,IChannelReconnectPolicybackoff on consecutive failures, and the standardmodv:pollingTimeper-affordance interval.IWotNodeSetResolver(recursive, ordered, unresolved namespaces reported) ships; no UA Cloud Library implementation, since that is a new external HTTP dependency and a deployment decision. That issue stays open for the resolver implementation itself.Review-round remediation (commit
3ea717089)An implementation review raised 11 findings (5 High, 6 Medium) and 12 review threads were open. All are addressed.
Correctness / robustness
IAtomicFileReplacecapability implemented byLocalFileSystemandVirtualFileSystem, rather than a newIFileSystemmember: that would break external implementers and is not expressible as a default interface member onnetstandard2.0. Blob storage now fails fast instead of silently degrading to a destructive write.ManagedSessioninstead of disposing it, so a failedForServerAsyncno longer closes the session other client facades are still using.ModbusTcpClientreconnects a faulted connection on the next transaction; backoff stays inPollingWotSubscriptionso there is only one retry loop.NodeSetComparerenforcesMaxXmlDepth, which was validated but never read, so deeply nested XML reports a diagnostic instead of overflowing the stack.WotMaterializationCoordinatorenforcesMaxResolverDocumentBytes, and decides namespace knownness from the live materialized closure because namespace-table entries outlive the NodeManagers that registered them.GetOrCreateGroupnormalizes names the way the registry service does; the resource output map is read under its gate.API
System.Nullablewrappers aroundINullabletypes across the WoT and registry surface, includingTrySelectProjectionRoot.WotThingCatalog, and the synchronous core only performs lookups — so the Roslyn source generator stays synchronous and no sync-over-async is introduced.ExtensionObject.TryGetValueto decode through the codecs and reduced theVariantstructure helpers to delegation, since the context-taking API already existed.NodeSetComparer.CompareXmltakesReadOnlySpan<byte>instead ofbyte[].Layout
samples/WotCon/with theWotprefix dropped from folder, project, assembly and namespace names.NodeSetComparermoved next to the other NodeSet code inOpc.Ua.Export.MemoryWotBindingmoved out of the shipping bindings library into the aggregation sample and the test project.Sync-WotConModels.ps1(belongs in the drafts repository); renamed the pinned NodeSet2 files toWotConcasing; reverted a blank-line-only change inSession.cs.Server lifecycle
RequestManagerinto an optional registerable extension. With no extension registered the behaviour is exactly master's. The AsyncLocal scope nesting stays inRequestManager: it fixes scope propagation for the pre-existing scopes rather than adding a feature.StandardServerdisposal is documented and idempotent.Subscription/Sessionand server-shutdown threads asked for investigation and proposals before changes, so those are analysed and answered on the threads rather than rewritten here.Validation: full
UA.slnxbuilds clean on net10.0 (0 warnings) and net48 (0 errors). Suite totals on net10.0:Opc.Ua.Types.Tests8 408,Opc.Ua.Server.Tests3 978,Opc.Ua.WotCon.Tests920,Opc.Ua.WotCon.Bindings.Tests512,Opc.Ua.SourceGeneration.Core.Tests3 743,Opc.Ua.SourceGeneration.Tests112,Opc.Ua.XRegistry.Tests171,Opc.Ua.Di.Tests310,Opc.Ua.ISA95.Tests137.Server shutdown hardening (commit
46e5e5f2d)Follow-up to the
StandardServer.csreview thread, which asked for a thorough review of the server lifecycle handling with proposals before merge. The structural proposals remain open on that thread and no refactor was done; what landed here are the concrete defects that are wrong under any of those proposals:m_shutdownCoordinationLockwhileShutdownServerInternalAsyncnow starts outside it. Previously every statement before its firstawaitexecuted under the lock.ActiveTaskis still published inside the lock, so concurrent callers keep joining a single shutdown.ServerErrorand a source-generated log instead of only being recorded on the shutdown state.Final node-manager cleanup deliberately keeps
CancellationToken.None: cancelling it would leak retired lifecycle state, node managers and the server semaphore after request admission has already been closed permanently. Those call sites are now commented to make the intent explicit.Opc.Ua.Server.Tests: 3 980 passing, 0 warnings.Open review threads
Three threads are intentionally left unresolved because they ask for a joint decision rather than a specific change, and each has a full written analysis posted:
StandardServer.cs— three ranked structural proposals for the shutdown coordinator, awaiting a direction.Subscription.cs— a four-capability inventory of what this PR adds versus whatmasteralready supports, with a keep/refactor/revert recommendation per capability.IMonitoredItem.cs— answered: immediate generation retirement is the one scenario master's detach/park/recover lifecycle cannot express.