mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-17 05:11:13 +08:00
master
8 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2ba58a8cbf |
WoT: move GeneratesEvent off Object instances (OPC 10000-3 §7.15) (#4706)
Fixes #4669. OPC 10000-3 §7.15 says the SourceNode of `GeneratesEvent` "shall be an ObjectType, a VariableType or a Method". CTT `Base Info Core Structure 2 001.js` flags every Object that is the source of one. #4667 fixed this in the source generator. This PR fixes the three remaining WoT places. ### 1. WotCon asset registry - `AssetRegistry.BuildEventNode` / `RemoveEventTypes` no longer add or remove `GeneratesEvent` from the asset Object. Event delivery already relies on `EventNotifier = SubscribeToEvents` and root-notifier registration. - Tests now assert the reference is **absent** and the notifier bit is set. The re-apply test asserts that the old EventType node is gone. ### 2. WoT NodeSet converter (`src/Opc.Ua.Types/Wot`) - **TD → NodeSet:** a last pass (`RelocateInstanceGeneratesEvent`) moves forward `GeneratesEvent` references off an Object/Variable root: - An event the bound type already declares needs nothing more. Matching is by qualified BrowseName against the local context's declarations, the same rule as the declaration merge. - Any other event is declared by a synthesized non-abstract `<rootLocal>Type`. It derives from the bound type (or from `BaseObjectType` / `BaseDataVariableType` when the TD binds none) and becomes the instance's `HasTypeDefinition`. A Variable root gets a `UAVariableType` with the instance's DataType and ValueRank. - Thing Models are unchanged: the projected type keeps its `GeneratesEvent`. - **NodeSet → TD:** - An instance's event affordances are now collected from its type definition and that type's supertypes. Instance-level `GeneratesEvent` is still read for third-party NodeSets. - In a single readable document, a type that only carries one instance's events (only `HasSubtype` + `GeneratesEvent`, typing exactly one instance) is not chosen as root. It is folded back: its supertype becomes the type binding, and no link is written when that supertype is the default base type. The TD → NodeSet → TD round trip stays readable, with no `uav:nodes` fallback. - A document set gives the carrier its own Thing Model, and the instance TD binds to it. - **Archive check:** an event affordance is accepted when the root's type definition, or one of its supertypes, generates it. - `docs/WoTNodeSetConversion.md` documents the behaviour in a new "Event source" row. ### 3. Aggregation sample - `SamplePump.NodeSet2.xml`: each pump is typed by its own `PumpType` subtype, `ns=1;s=Pump1.Type` (`Pump_1Type`) and `Pump2.Type`, which declares its alarm EventTypes. - It is per pump because each pump owns its alarm EventTypes. `Pump2.Type` is cloned with the rest of Pump2. - The pumps keep `EventNotifier="1"` and no longer carry `GeneratesEvent`. - The checked-in documents are regenerated, adding `sample-pump-pump-1type.json` / `-2type.json`. The pump TDs still list the alarm event affordances. - The sample binding lookup ignores Thing Models, because the type's TM declares the same EventType as the pump TD. A declaration missing from the instance still fails as before. - Docs: `WoTConnectivity.md` (asset event affordances, §12.4.2) and `WotBindings.md`. The `GeneratesEvent` clause in the sample-limitation note was already stale, so I removed it. ### Runtime check `WotNativeOwnershipIndex` only uses `GeneratesEvent` in its alias table. Projected event routing takes the owner from `uav:componentOf` / `uav:id` and the notifier from Parent/`HasNotifier`. Nothing in `Opc.Ua.WotCon.Server` reads an instance-level `GeneratesEvent`. ### Tests (net10.0) - `Opc.Ua.Types.Tests`: 11446 passed, 1 skipped. New `WotInstanceEventSourceTests` covers object and variable roots, TMs, the TD with no events, the folded round trip, the legacy instance reference and the document set. Two bound-type cases were added to `WotDeclarationMergeBoundaryTests`. - `Opc.Ua.WotCon.Bindings.Tests`: 714 passed. - `Opc.Ua.WotCon.Tests`: 1496 passed, including the byte-exact sample regeneration tests. - net48 build of both test projects is clean, and `dotnet format whitespace` is clean. Not run: CTT `Base Info Core Structure 2 001.js` (p02-base-info) against a WotCon server. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Marc Schier <marcschier@users.noreply.github.com> |
||
|
|
e8bec27d5d |
Complete WoT binding preservation and two-pump aggregation (#4431)
# Description
Complete WoT Binding preservation and the two-pump aggregation workflow
without implicit overlays or multi-source action fan-out.
- Add verified linked-document export and bounded partition merging.
Preserve complete source facts, reject contradictory archival content,
and retain usable readable content when a future native grammar is
unsupported.
- Wire local Methods and Condition events through the existing
injectable binding/runtime infrastructure. Keep local and upstream
identities distinct, translate namespace-bearing values, and bound
occurrence routes by source and generation.
- Wait for asynchronous event-producer readiness before completing
subscription creation, so the first alarm is not lost. Preserve
cancellation, teardown, and startup-failure reporting.
- Import runtime types in inheritance order and attach typed Method
argument Properties. Keep generated Double spelling deterministic across
supported frameworks and remove an AOT-unsafe registry validation
conversion.
- Generate both pumps and their asset/group projections. Expose fifteen
typed readings per pump, separate source-owned controls, and
acknowledge/confirm/reset workflows. Extend the demo script for managed
and NativeAOT execution, bounded failures, and exact-process cleanup.
- Synchronize current-state documentation and specification fixtures.
Compose fixture paths from platform-independent components and emit/pin
LF pump XML so byte-exact regeneration holds on Windows, Linux, and
macOS.
- Keep sample JSON readable with sorted members, two-space indentation,
and LF line endings. Normalize generated filenames to single hyphens
without changing registry resource IDs, document values, logical links,
or dependency ordering; the manifest maps resource IDs to their physical
filenames.
Most changed files are deterministic regenerations of checked-in sample
documents. The demo manifest contains 533 linked resources; the
aggregation server remains generic and has no Pump-specific compiled
model dependency.
No new alarm vocabulary, shelving/suppression/dialog/ConditionRefresh
transport mapping, Azure SDK changes, or complete Pump/OpenUSD parity is
introduced.
## Related Issues
Fixes #4430, which records the approved implementation scope, design
decisions, and acceptance criteria.
Follow-up to #4411. Specification fixtures are pinned to
[OPCF-Members/spec-drafts commit
82620d9](https://github.com/OPCF-Members/spec-drafts/commit/82620d9e313f35386dfc7477f7875cd47d2fd268),
associated with OPCF-Members/spec-drafts#35.
## Validation
- .NET 10 and .NET 8: complete affected converter/schema, WoT
Connectivity, and binding suites passed.
- .NET Framework 4.8: affected libraries, converter/generator, projected
runtime, readiness, and NodeSet-import compatibility runs passed. The
existing filesystem test exceeding the host's legacy path limit passed
through a temporary short-path alias, without changing its assertions.
- All 29 aggregation sample tests passed, including the independent
companion-model oracle.
- Managed/default-port and NativeAOT/alternate-port demo runs completed
with 533 documents, two pumps, thirty Good readings, and all four
pump/source control and alarm workflows.
- Timeout and duplicate-port failure paths were exercised; owned
processes and ports were released.
- Specification artifact/rule checks, byte-identical example mirroring,
statement-digest verification, and changed-file whitespace checks
passed.
- The [Build and Test .NET 10.0 workflow for
|
||
|
|
90fc3cca2f |
Complete WoT Binding 1.1 alignment and conformance coverage (#4411)
# Description Completes the UA-.NETStandard implementation of the final OPC UA WoT Binding 1.1 semantics and aligns the WoT Connectivity 1.1 model with xRegistry 0.4.0. The change: - retires `uav:isEvent` and `uav:severity` from current Binding output while preserving unknown members through the generic converter's ordinary residue mechanism; - removes `uav:severity` from the asset Thing Description parser and materializer, so document metadata cannot set occurrence severity; - implements logical EventType definitions, ordered resolution providers, explicit/default selection precedence, materialized member paths, and the built-in `BaseEventType` fallback; - adds deterministic portable identity and browse-path anchoring; - populates bound type declarations instead of creating duplicate siblings, including inherited and closed-content validation; - resolves and validates `uav:externalSchema` without overriding authoritative DataType definitions; - hardens DataType dimensions, native projections, preservation envelopes, URI transmission, projection limits, and delete-policy handling; - synchronizes the xRegistry and WoT Connectivity source-generation inputs; - pins all 27 specification examples, all 97 implementation-owned normative requirements, and all 12 cross-language vector groups to the final specification revision; - adds `samples/WotCon/run-aggregation-demo.ps1` for a verified one-command aggregation client/server round trip and replaces the README topology sketch with Mermaid; - normalizes redirected PowerShell diagnostic presentation in the statement-digest test harness so its semantic assertions are stable on Windows, Linux, and macOS. WoT Connectivity remains model version `1.1`; draft iterations are identified by release labels such as `1.1-draft5`. **Specification dependency:** the evidence assets pin `OPCF-Members/spec-drafts` `main` commit `25db507121b503fd31c8816fbfae67a6822e813b`. The corresponding specification follow-up is `OPCF-Members/spec-drafts#35`. ## Validation - Latest GitHub/Azure CI: 266/266 checks passed. - .NET 10 feedback-focused tests: - WoT Connectivity event/model/traceability: 72 passed - binding capability/traceability: 8 passed - coverage-gate tests: 25 passed - Complete .NET 10 Tools project: 752 passed, 1 intentionally skipped. - .NET Framework 4.8: - WoT Connectivity event/model/traceability: 72 passed - WotCon bindings build: 0 warnings, 0 errors - NativeAOT build: 0 warnings, 0 errors. - WoT aggregation demo: 16 documents uploaded and all 10 mapped values read with Good status; exact processes and isolated PKI stores cleaned up. - Specification traceability: 402 normative statements mapped; all 97 implementation-owned statements have passing test evidence with zero gaps. - Shared conformance vectors: 12/12 groups execute. - Final CI coverage: - project line coverage: 86.83% (floor 70%) - project branch coverage: 76.71% (floor 60%) - patch coverage: 100.00% (2,591/2,591 changed lines) - coverage gate passed ## Related Issues Follow-up to #4375 and alignment with `OPCF-Members/spec-drafts#31` and `OPCF-Members/spec-drafts#35`. No separate tracking issue was opened for this follow-up. ## 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 in the changed paths. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [x] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [x] I have addressed all PR feedback currently received. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6a9f41b1-72f0-4c74-bd7f-26fa531e5738 |
||
|
|
81f200c9d2 |
Align WoT conversion and runtime with Binding 1.1 (#4375)
## Summary Align the generic WoT/NodeSet converter, WoT Connectivity registry, and runtime binding implementation with the OPC UA WoT Binding 1.1 draft. ### Conversion and preservation - fix the standard placeholder ModellingRule NodeIds; - make every produced/restored NodeSet importable by completing required aliases; - support `ua:ComponentOf`, arbitrary companion ReferenceTypes, BrowseNames, InverseNames, symmetric references, and exact direction; - enforce the projection annotation allowlist and deterministic total order; - emit and consume Method argument schemas, EventType data, Alarms & Conditions, event severity, engineering units/ranges, localization, ValueRank, and ArrayDimensions; - preserve unsupported content through JSON residue/native projection rather than silently dropping it; - compare residue conflicts with RFC 8785 canonical equality while retaining byte-exact residue digests; - stamp generated documents with `uav:bindingVersion: "1.1"`. ### Runtime bindings - resolve an EventType Thing Model referenced with `tm:ref`, recursively derive its `data` fields as the baseline selection, and overlay explicit `uav:eventSelectClauses` that also reference EventType TMs; - pre-resolve EventType links asynchronously through the bounded sibling-document resolver before synchronous protocol planning; - retain read compatibility with legacy implementation-specific `uav:eventFields`; - expose nested event `data` matching the TD while retaining the flat transport field index for compatibility; - apply minimum-security constraints through deterministic endpoint selection and fail-closed session verification; - use one Unicode code-point comparer for projection membership and endpoint ties. ### Validation and fixtures - add strict, profile-aware validation while keeping default parsing permissive and residue-preserving; - pin 27 normative examples byte-for-byte to the specification repository, with an offline SHA-256 manifest and regeneration test; - regenerate checked-in WoT documents with the final canonical writer and binding version; - document the generic converter separately from the legacy OPC 10100-1 asset path. ## Notable compatibility changes - converted NodeSets may gain a completed `<Aliases>` table so standard ReferenceType names can be imported; - previously inverted/dangling placeholder ModellingRules are corrected; - generated documents gain binding-version, localization, rank/dimension, and exact ReferenceType metadata; - event data now has the nested shape described by the binding; - the former NodeId-based `uav:typeDefinitionId` clause is removed; event selections now use EventType-TM `tm:ref` links; - `uav:eventFields` remains readable but new documents use `uav:eventSelectClauses`; - unknown well-formed future revision/profile claims produce unsupported warnings rather than strict rejection. ## Generated files Most of the 621-file diff is deterministic regeneration: 517 checked-in WoT documents gain the binding version and final canonical spelling. Regeneration was run twice and is byte-idempotent. ## Validation - `dotnet restore UA.slnx`; - `dotnet build UA.slnx -c Release` across all configured TFMs; - `Opc.Ua.Types.Tests`: 9,215 net10.0; 9,208 net48; 9,208 net472; - `Opc.Ua.WotCon.Bindings.Tests`: 611 each on net10.0, net9.0, and net8.0; - `Opc.Ua.WotCon.Tests`: 1,136 net10.0; - `Opc.Ua.WotCon.Samples.Tests`: 14 net10.0; - NativeAOT test executable: 136/136; - all 27 strict fixture conversion/import checks; - formatting, analyzers, `git diff --check`, and regeneration idempotence. The full net48 run retains one unrelated existing MAX_PATH failure in `FileWotRegistryStoreTests.MultiLevelDirectoryCreationSyncsEveryModifiedParentInOrder`. Solution-wide stress runs also include an intentional 60-minute soak and unrelated locale-sensitive/flaky tests; all affected WoT projects and tests pass. ## Specification Depends on OPCF-Members/spec-drafts#31. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6a9f41b1-72f0-4c74-bd7f-26fa531e5738 |
||
|
|
d525bc3f28 |
Bound reconnect cleanup and organize samples by domain (#4276)
# Description This PR fixes bounded ManagedSession reconnect cleanup and reorganizes the sample applications into domain-focused folders. ## Reconnect cleanup - Bounds reconnect integration cleanup with independent 30-second close and dispose limits. - Sends `CloseSession` through the awaited transport request path during `Session.DisposeAsync`. - Preserves explicit `Session.CloseAsync` behavior that waits for or cancels outstanding publish requests. - Covers the disposal request path with a regression test. ## Sample organization - `samples/OpenUsd`: `GeneratorServer`, `SiteCompositionServer` - `samples/Redundancy`: `RedundantClient`, `RedundantServer`, `RedundantPubSub` - `samples/Isa95`: `MinimalIsa95Server` - `samples/MinimalApi`: `MinimalBoilerServer`, `MinimalCalcServer`, `MinimalClient` - `samples/DI`: `PumpDeviceIntegrationServer` - `samples/Lds`: `ConsoleLdsServer` - `samples/Reference`: `ConsoleReferenceClient`, `ConsoleReferenceServer` - `samples/PubSub`: `ConsoleReferencePubSubClient` `UA.slnx` now exposes the same requested domain groups. Project references, tests, runtime sample probes, CI workflows, Dockerfiles, Compose files, helper scripts, and documentation were updated to the new paths. `samples/Robotics/MinimalRobotServer` remains under `Robotics`. ## Validation - `dotnet build UA.slnx` - 3 Redundancy short-haul sample tests - 70 DI sample tests - 914 OpenUSD tests - 1 WotCon model-materialization test - 137 AOT tests - `docker compose config` for all 6 moved Redundancy Compose files ## Related Issues - Fixes #4273 ## Checklist - [ ] 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 built the complete `UA.slnx` solution locally. - [ ] I ran all tests locally against at least .NET Framework and .NET 10. - [ ] I fixed all failing and flaky tests in the CI pipelines and all CodeQL warnings. - [x] I have addressed all PR feedback received. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: marcschier <11168470+marcschier@users.noreply.github.com> Co-authored-by: Marc Schier <mschier@microsoft.com> Co-authored-by: Marc Schier <marcschier@users.noreply.github.com> Copilot-Session: a2c2838f-74ae-4997-a1d4-50574d9e0636 |
||
|
|
a7c776e8d4 |
Align the WoT implementation with spec: type binding, removed vocabulary, Alarms and Conditions (#4225)
# Description Aligns the WoT implementation with the normative changes in the OPC UA WoT specification drafts that the stack had not picked up, and fixes the defects that alignment surfaced. The driving artifact is [OPCF-Members/spec-drafts#19](https://github.com/OPCF-Members/spec-drafts/pull/19), *"bind a projected node to an already-loaded type, by name or by NodeId"*, which landed on the spec `main` after this repository last tracked it. ## 1. Bind a projected node to an existing type (Binding §5.2.1) §5.2.1 lets a document state that the node it projects is an instance of a type that **already exists** — one a companion model defines and the Server has loaded, or one a sibling document of the same conversion defines — so a converter binds to that type instead of defining a second type of the same shape. The converter hardcoded `HasTypeDefinition` to `BaseObjectType`, so a document that named its type projected an **untyped** node: a client browsing for the companion type would not find it, and nothing said why. Both forms are now implemented, resolved against the §5.1.5 **local context** — the other documents being converted together first, a loaded AddressSpace as the fallback. `IWotNodeResolver` supplies that context (is a namespace held; what does a qualified BrowseName match; what does an ExpandedNodeId identify), `WotCompositeNodeResolver` composes implementations in the specified order, and `NullWotNodeResolver` is the default that holds nothing. The converter needed no new async surface: the binding is resolved in a pre-resolve pass, exactly as Thing references already are, and the synchronous core consumes the result. The §5.2.1 table is honoured: a unique name binds; the link settles an ambiguous name; a name that resolves to nothing while the link resolves is **invalid** (that is a mistake in the name, not a shorthand for the identifier); two forms resolving to different Nodes are invalid; a resolved type of the wrong NodeClass is invalid. A binding naming a type the local context does not hold **fails** rather than falling back to `BaseObjectType` — a silently mistyped node is worse than a reported failure. A compact name is told from ordinary `@type` annotation **by namespace**, not by whether the lookup succeeds, so `saref:TemperatureSensor` on a Server that has never heard of SAREF stays the annotation it always was. The definitive form — a `ua:HasTypeDefinition` link whose `href` is the ExpandedNodeId of the type. An ExpandedNodeId matches exactly one Node or none, so it needs no lookup. A Node has exactly one `HasTypeDefinition`, so: - two such links → `AmbiguousTypeBinding` (6019), and the root keeps the default rather than being bound to a guess; - a link with no usable identifier → `InvalidTypeBinding` (6020); - no binding at all → the `BaseObjectType` default is unchanged. ## 2. Drop the six vocabulary terms `#19` removed `#19` deleted `uav:capability`, `uav:componentModel`, `uav:reference`, `uav:congruentType`, `uav:congruentTypeName` and `uav:nameNamespace` from the context, the JSON Schema and the ontology. A link now names its ReferenceType directly in `rel`, so the three link relations become `ua:HasInterface`, `ua:HasComponent` and `ua:NonHierarchicalReferences`, and the congruent-type pair is superseded by the §5.2.1 type binding. Their validation and mapping are removed; they are now ordinary residue like any other unrecognised term. ### A real gap this uncovered The converter knew **eight** ReferenceTypes by name and had neither `NonHierarchicalReferences` (`i=32`) nor `HasInterface` (`i=17603`) — so the two relations that *replace* `uav:reference` and `uav:capability` could not resolve at all. That includes example 02, which this repository vendors as a golden asset and which `#19` rewrote to use them. Both are now in the map. `uav:congruentType` was also the only term that redirected one reference to another while resolving a link target, so `ResolveTargetNodeIdAsync` no longer needs a loop. It resolves once and reports an unresolved target; the resolution context is still entered and the bytes still counted, so the per-conversion document, depth and byte bounds are unchanged. ## 3. Reject an out-of-range event severity instead of clamping it `uav:severity` had grown in this implementation without being a specification term. Rather than drop it, it is proposed upstream in [OPCF-Members/spec-drafts#21](https://github.com/OPCF-Members/spec-drafts/pull/21), which defines it with an explicit rule: the value **shall** be in the OPC 10000-5 range 1..1000, and a consumer **shall not** silently clamp it. The implementation clamped, so a document asking for severity 5000 was accepted and published as 1000 — the mistake was hidden while what the author asked for was changed. An affordance carrying an out-of-range severity is now skipped, with the reason logged, and its half-built event type and `GeneratesEvent` reference are removed. ### Defect found while making that change The occurrence-time path read `ValidateSeverity(severity) ?? tag.Severity`. Because `ValidateSeverity(null)` returns the 500 default, it **never** fell back to the affordance's authored severity: an occurrence reported without a severity published 500 rather than the authored 900. Build-time validation and the occurrence-time choice are now separate methods. ## 4. Vendored examples and honest coverage claims Examples 21 and 22 were missing from the vendored golden assets. Converting example 22 is now the end-to-end check for §5.2.1 — the specification's own example rather than a fixture written to match the implementation. `docs/WotBindings.md` claimed ten of the eleven conformance units and silently omitted **WoT-ConditionMapping**, and said "twenty worked examples". The count is corrected, and WoT-ConditionMapping is now **covered** — see §5 below. ## 5. Alarms and Conditions (Binding §13) The last unclaimed conformance unit. A projected Condition event now derives from the ConditionType it names rather than from `BaseEventType`; falling back would lose the Condition state model entirely, leaving a Client unable to tell an alarm from an ordinary event. The two forms follow the hint-plus-pin pattern of §5.3: `uav:conditionTypeId` is definitive and wins, `uav:conditionType` is a readable hint resolved for the four ConditionTypes §13.1 scopes. An unpinned name outside that set is reported rather than guessed. Four conformance rules are enforced, each because breaking it yields a document a consumer can read but cannot act on: | Rule | Section | Diagnostic | | --- | --- | --- | | A Condition event declares `EventId` in its `data` | 13.3 | `ConditionEventIdMissing` | | `uav:conditionAction` is inside its closed set | 13.2 | `InvalidConditionAction` | | `uav:actsOn` names a Condition event in the same document | 13.4 | `InvalidConditionTarget` | | `Acknowledge`/`Confirm`/`AddComment` declare an `EventId` input | 13.4 | `ConditionActionInputMissing` | `Enable` and `Disable` act on the Condition instance rather than one occurrence and are deliberately exempt from the last rule, pinned by a negative test. Shelving, suppression, dialog conditions and `ConditionRefresh` stay out of scope, as §13.1 scopes the mapping. ## 6. The sibling half of the §5.1.5 local context `SnapshotWotNodeResolver` indexes the registry snapshot a conversion runs over and is wired into `WotNodeSetDocumentConverter`, so a document naming a type another document in the same registry projects resolves with no AddressSpace at all. Only Thing Models are indexed: a Thing Model projects a `UAObjectType` and is a valid target, a Thing Description projects an instance and never is. Identity comes from the new public `WotNodeSetConverter.TryDescribeProjectedType`, which applies exactly the rules the conversion uses, so an index entry and the projected node cannot drift apart. Ambiguity is preserved rather than resolved: two siblings sharing a qualified name yield both matches so the caller can report it. ## Deliberately not in this PR - **The AddressSpace-backed `IWotNodeResolver`.** The abstraction, the composite, the null default and the sibling-document implementation ship here; the AddressSpace-backed half needs a cached type-hierarchy walk behind a server `OperationContext` and follows separately. Until it exists a host gets the sibling context plus the null fallback, which reports a binding as unresolved rather than mistyping it. - **Two §5.2.1 obligations that need the address space**: not duplicating a mandatory instance declaration of the resolved type, and rejecting disagreement with an instantiated Thing Model. - **Connectivity §7.3 parent resolution.** Each is tracked; none is faked here. ## Related Issues There is no tracking issue: this came out of a spec-versus-implementation audit rather than a reported defect, and the driving artifacts are the two upstream spec PRs linked above. Happy to open one if maintainers prefer — the §5.2.1 follow-up in particular is worth an ADR, since the local-context resolver is a new injectable provider spanning two assemblies. ## 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. ### Verification detail | | net10.0 | net48 | |---|---|---| | `Opc.Ua.Types.Tests` | 8573 / 8573 | 8555 / 8555 | | `Opc.Ua.WotCon.Tests` | 1075 / 1075 | 1075 / 1075 | Full `UA.slnx` build: **0 errors, 0 warnings** across all target frameworks, re-verified after `master` was merged into the branch. Every new test was mutation-verified: the type-binding tests against three mutations (revert to the hardcoded default, drop the ambiguity check, accept an empty `href`), the severity tests against reinstating the clamp, and the ReferenceType additions against removing them from the map. Each mutation failed the suite before the change was kept. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8 |
||
|
|
fb80bfdc82 |
[WotCon] Track WoT Connectivity/Binding 1.1 and add projections (Section 12) (#4182)
Follow-up to #4128, which merged the WoT Connectivity 1.1 work against **draft2** of the companion specifications. Both drafts have moved since, and this branch tracks them through to **draft3**, adds the projection feature they introduced, and closes the gaps a bidirectional re-audit found. The specifications are the private [`OPCF-Members/spec-drafts`](https://github.com/OPCF-Members/spec-drafts) drafts of *OPC UA — WoT Connectivity 1.1* and *OPC UA — WoT Binding 1.1*. They are still drafts, which is why this is opened as a draft PR — see [Status](#status) at the bottom. ## What changed in the specifications Two revisions landed upstream while #4128 was in flight. **draft2** replaced the `eventGroup` / `actionGroup` grouping constructs with a single **projection / view** concept, added fifteen Section 6 vocabulary terms, and made two identifier rules normative. **draft3** ([spec PR #8](https://github.com/OPCF-Members/spec-drafts/pull/8)) un-deprecated the incorporated OPC 10100-1 v1.02 surface, restructured the conformance clause into three profiles, and tightened two security obligations. The draft3 NodeSet is **structurally identical** to draft2 — I diffed all 286 nodes node-by-node. Every change is metadata: 96 `ReleaseStatus="Deprecated"` attributes removed, `PublicationDate` advanced, and `WOTC-ProjectionMaterialization` claimed by three Type Nodes that previously implemented it in name only. ## Projections (WoT Binding Section 12) The substantial addition. A projection document selects affordances from other Thing Descriptions or Thing Models and resolves to a document of its own; materialized, it becomes an OPC UA `View` that **creates no affordance Node** and only `Organizes` what its sources already materialized. | Piece | Where | |---|---| | Document model and validation | `Opc.Ua.Types/Wot/WotProjection.cs`, `WotProjectionModel.cs` | | Four-stage resolution engine | `Opc.Ua.Types/Wot/WotProjectionResolver.cs` | | Materialization plan | `Opc.Ua.WotCon.Server/Materialization/WotViewProjectionPlan.cs`, `WotProjectionViewBuilder.cs` | | Live address-space host | `WotProjectionViewNodeManager.cs`, `LifecycleWotViewProjectionHost.cs`, `IWotViewProjectionHost.cs` | | Node lookup seam | `IWotMaterializedNodeIndex.cs` | Groups reached by `ua:Organizes` become organizational Objects rather than nested Views, only the outermost materialization is a `View`, and a member whose source is served by another server is omitted and reported rather than recreated — the federation case the clause calls out. ### How this is validated The specification publishes twenty worked examples, two of which are a **golden pair**: a projection document and the resolved view it is defined to produce. `WotSpecExampleTests` embeds all twenty and runs the pair through the resolver, asserting against *the specification's own expected output* rather than against my reading of the prose. That single document exercises all three selection forms, the bulk naming rule, the security closure naming and the provenance term. This is the strongest evidence in the PR, and it is worth more than the unit tests around it: it is the same check the upstream `validate_local.py` performs. ## Other spec work - **Section 6 vocabulary** — fifteen missing model and platform terms with their validation rules (`WotNodeSetConverter.ModelVocabulary.cs`). - **Portable identifiers** — the two rules release 1.1 made normative, with `WotNodeSetConverterOptions.AllowNonPortableIdentifiers` as a documented downgrade-to-warning switch for documents authored against v1.00. Defaults to `false`. - **xRegistry §6.9 identifier construction** (`XRegistryIdentifier.cs`) — reverse-authority symbolic identifiers; all seven published worked examples are test cases. ## Gaps found by re-auditing against draft3 I audited in both directions — every normative requirement to code, and every `uav:` term in code back to a spec that defines it. Three findings, all fixed: 1. **`Wot-Con 1.02` accepted anything well-formed.** draft3 makes format validation part of the unit, because *WoT-Con Minimal* is one unit wide and materialization projects a *valid* document. The 1.02 upload path only deserialized into the `ThingDescription` POCO, which happily accepts JSON that is not a WoT document at all — every member of that type is optional, so `{}` deserialized and was materialized. It now parses via `WotDocument.Parse` and requires the document to identify itself by a non-empty `name` or `title`, returning `Bad_DecodingError` and materializing nothing otherwise. The rule lives in `ThingDescriptionFormatValidator` and is shared with `CreateAssetForEndpoint`, whose auto-generated Thing Descriptions §11 also treats as untrusted input. 2. **Two `WoTFile` operations were ungated.** draft3 §11 names `Write` and `CloseAndUpdate` alongside the five management methods; both reached the materializer without an access check. `EnforceManagementAccess` is now wired into `WotAssetFileManager`. 3. **Two invented `uav:` terms.** `uav:NodeSet2Preservation` was declared and never used — removed. `uav:eventFields` is read by `OpcUaBindingPlanner` and defined in *neither* spec, while a comment claimed it came from OPC 10101. It still works for compatibility but is now documented as a non-standard extension at both sites rather than misattributed. The SSRF prohibition is satisfied by construction: there is no HTTP client anywhere in these assemblies, and all resolution goes through the injected `IWotThingResolver`. ## Two ambiguities fixed in the specifications themselves The audit turned up two places where a conforming implementer had to invent an answer. Rather than pick silently, I took both upstream — they are pushed to [spec-drafts PR #8](https://github.com/OPCF-Members/spec-drafts/pull/8) (all checks green) and are now normative in *Binding* §12.6 and *Connectivity* §7.13: - **`ViewVersion`** had one sentence saying only *that* it changes. It now changes iff the resolved membership changes, is updated at commit time alongside the `NodeVersion` stamping, and is a deterministic function of the membership alone in a canonical order — so two servers that resolved the same membership agree, which a per-server counter would not across a redundant pair. Explicitly **not monotonic**: compare for inequality only. - **"Type-level view"** was used once in §12.2 and defined nowhere, while every other statement of materialization was universal. §12.6 now *defines* it rather than contradicting §12.2: the NodeClass is `View` in both cases. Both were already what this code did, so neither changed behaviour — but the order-insensitivity the clause added was **not** covered by any test, and `ViewVersionIsUnchangedWhenOnlyTheOrderOfTheMembershipChanges` now holds it. It is mutation-verified: removing the ordinal sort turns it red. `docs/WotBindings.md` no longer lists these as points "the specification leaves open". ## Validation | Suite | Result | |---|---| | `Opc.Ua.Types.Tests` | 8525 ✅ (8518 on net48) | | `Opc.Ua.WotCon.Tests` | 1057 ✅ (net10.0 and net48) | | `Opc.Ua.WotCon.Bindings.Tests` | 559 ✅ | | `Opc.Ua.XRegistry.Tests` | 196 ✅ | | `Opc.Ua.Server.Tests` | 4027 ✅ | | `Opc.Ua.SourceGeneration.Core.Tests` | 3771 ✅ | | `Opc.Ua.WotCon.Samples.Tests` | 9 ✅ | The full `UA.slnx` builds with **0 errors and 0 warnings attributable to this PR** on all 6 TFMs (`TreatWarningsAsErrors` is on). `Opc.Ua.Server.Tests` and the source-generation suites are listed because this PR now also touches `MasterNodeManager` and the WoT-Con NodeSet. ## Compatibility No public API is removed or broken. Everything added is new surface using `ArrayOf<T>` / `ByteString` per the repo conventions, wired for DI with a direct-construction fallback, and the one behavioural tightening (`Wot-Con 1.02` format validation) rejects input that was never valid to begin with. One pre-existing issue is **not** addressed here because it came in with #4128 and is out of scope: `WotAssetFileManager.OnCloseAndUpdate` uses `.GetAwaiter().GetResult()`, which the repo rules ban. Worth a follow-up. ## Landed since this PR was opened - **DI upgraded to 1.05.0.** `ConnectsTo` was declared a subtype of `HierarchicalReferences`, contradicting OPC 10000-100 §5.5 Table 48. That is an upstream error in every official DI NodeSet up to 1.04, corrected by the OPC Foundation in 1.05.0, so the fixture is replaced with the verbatim 1.05.0 file rather than hand-patched. A guard test pins the corrected form. This also closed a second gap: six DI types the WoT model was missing are 1.05 additions. - **The sample Pump has the type definition OPC 40223 declares for it.** `Pump1.Identification` carried DI's `FunctionalGroupType` instead of `PumpIdentificationType`. Every test, the sample documents and the README agreed with each other about it, which is why it survived. - **`WotPumpAddressSpaceComparisonTests`** compares this server against `PumpDeviceIntegrationServer`, which builds the same OPC 40223 Pump from generated NodeSets and hand-written wiring. An independent oracle rather than a restatement of the same assumptions - it is what found the defect above. - **Error containment in `MaterializeProjectionViewsAsync`.** An exception mid-loop discarded the applied View handles before they were published, leaving View Nodes in the address space that nothing could remove. - **`HasWoTProjection`** is now created; it was documented and plumbed but never wired. - **The default in-memory View host** no longer deletes the View it has just applied. - **An undeclared NodeSet alias now says so.** It previously surfaced as `IdentifierMissing`, naming neither the value nor the fact that an alias was expected. - **`ViewVersion` follows the algorithm the specification now states.** My earlier spec text was rejected on review, correctly: it named neither the function nor the canonical order it demanded, ignored that OPC 10000-3 §5.4 requires a value greater than zero, and its "when, and only when" is unsatisfiable for a 32-bit value. §12.6 now specifies the algorithm outright and this implementation follows it exactly, pinned by an independently computed test vector. Implementing it surfaced a further defect in the clause - joining members on U+000A is not injective, because a NodeId string identifier may itself contain U+000A - which is fixed in the specification by length-prefixing each member. - **An auto-generated Thing Description is treated as untrusted input.** §11 requires it; `CreateAssetForEndpoint` validated the endpoint but handed whatever the discovery provider returned straight to the materializer. The `Wot-Con 1.02` format rule now gates both paths from one place. - **Document bytes moved out of the registry snapshot** into a content-addressed store, so a commit no longer rewrites the whole corpus to edit one document. Writes stage and are promoted during the commit, which preserves both durability-first ordering and the fail-closed rule that a blob directory without a manifest means operator recovery. Restoring streaming digest verification along the way closed a real gap: content integrity had degenerated to a length comparison. - **External references now reach a NodeManager registered later.** A reference to a Node that does not exist yet is dropped rather than queued, and the dynamic-registration path had no second phase to apply them in, so registering a NodeSet before the one it points into lost the forward edge permanently. Review of that change then found a state leak in its rollback path, fixed with a red-to-green regression test. - **`WOTC-Legacy` is renamed `Wot-Con 1.02`** in the specification and here. Editorial: the NodeSet diff is `<Category>` values only. ## Status Open for review. Two things may still move: 1. **The specifications are unpublished drafts.** Anything here can change if the working group decides differently. The `ViewVersion` definition has since been settled - §12.6 specifies the algorithm and this follows it - but the same could happen to any other clause tracked here. 2. **`uav:eventFields`** is kept as a documented non-standard extension. That is a judgement call the WG may want to reverse, either by adopting the term or removing it. Two known items are recorded rather than fixed: - `Pump1` has no hierarchical parent, so it is reachable by NodeId but not by browsing down from `Objects`. The cross-NodeSet ordering defect this was originally attributed to is fixed above, and cross-manager references are now symmetric in both registration orders. The Pump itself is still blocked on a different cause found while verifying that fix: with the reference added, `DeviceSet` exists but the Pump has **zero** inverse references, so the edge is dropped at *import* - before any external-reference machinery runs. That is a separate defect in the materialization path, not the one that was fixed, and it is left recorded rather than guessed at. - `WotAssetFileManager.OnCloseAndUpdate` uses `.GetAwaiter().GetResult()`, which the repo rules ban. Pre-existing from #4128 and out of scope here. Review of the projection resolver and the materialization host is the most valuable thing to look at; the vocabulary and identifier commits are mechanical by comparison. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8 |
||
|
|
e73e71184d |
[WotCon] Add OPC UA WoT Connectivity 1.1 with the Types, source generation, 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 |