Files
UA-.NETStandard/docs/Aas.md
Marc SchierandCopilot 397db603db Add OPC UA for Asset Administration Shell: the published OPC 30270 and v3 draft (#4244)
# Description

Implements the **Asset Administration Shell** companion specifications
on top of `marcschier/wot-type-binding`, in two halves that a Server
chooses between:

* **AAS V3** — the
[draft](https://github.com/marcschier/opcua-drafts/tree/main/companion-specs/AAS)
(`http://opcfoundation.org/UA/I4AAS/v3/`), all sixteen conformance
units: the metamodel, the registry built on the abstract xRegistry base,
generational materialization, federation, packages with digest-bound
integrity, the Digital Product Passport vocabulary, and the Annex F
bridge to WoT Thing Descriptions.
* **AAS V2** — the published [OPC
30270](https://reference.opcfoundation.org/specs/OPC-30270/full)
(`http://opcfoundation.org/UA/I4AAS/`), which maps the AAS V2.0.1
metamodel: object model, ingestion of XML, JSON and AASX,
materialization, server and client.

The two are different metamodel generations rather than two revisions of
one model — V2 keeps `Asset` as a first-class Identifiable,
`identification.idType`, Views and ordered collections, none of which
survive into V3 — so they are **alternatives**. A Server hosts one or
the other; this is documented rather than enforced at startup. Each is
named for the generation it maps (`Opc.Ua.Aas.V2`, `Opc.Ua.Aas.V3`,
`AddAasV2Server`, `AddAasV3Server`), and what genuinely serves both
carries no marker and sits at the root of `Opc.Ua.Aas`: the
deterministic NodeId encoding and idShortPath conventions,
`AasOptional<T>`, the digest rule, the materialization diagnostics, the
AASX container walk, and the value and operation providers, which are
keyed on `NodeId` and `Variant` and so carry no version at all.

## What is in here

**New libraries**: `Opc.Ua.Aas`, `Opc.Ua.Aas.Server`,
`Opc.Ua.Aas.Client`, `Opc.Ua.Aas.WoT`, plus `samples/Aas` and
`docs/Aas.md`.

**Enabling changes to existing code**, each needed by the above:

* The OPC UA `Decimal` DataType added to `Opc.Ua.Types`, with its Part 6
§5.1.10 wire encoding. `BinaryDecoder` gained one internal member,
`TryReadRemainingBodyBytes`, because a trailing field with no
self-describing length cannot otherwise be read; `Decimal.Decode` is its
only caller. Part 6 Table 3 types the unscaled `Value` as an
**OctetString whose byte count is `Length` − 2**, not a length-prefixed
`ByteString`, so it carries no length of its own —
`DecimalEncodingTests` pins the body byte-for-byte against that mistake,
and against the `Length ≤ 2` values the same table calls invalid.
* The xRegistry projection engine extracted into
`Opc.Ua.XRegistry.Server` and `Opc.Ua.WotCon.Server` migrated onto it
(−848/+387 lines), with the WoT Connectivity tests passing **unedited**.
* Two source-generator fixes, both of which made a model impossible to
build (see below).

## Defects found and fixed along the way

Running the stack end to end, rather than trusting per-area test suites,
surfaced defects that no unit test caught. The ones worth a maintainer's
attention:

* **The generator emitted non-terminating constructors.** A structure
field with no declared default was initialized with `new T()`, so a
self-referential DataType (`AASReferenceDataType.referredSemanticId`)
recursed until the stack overflowed and `AddOpcUaAas` could not load the
model at all. The test host *died* rather than failing. Only the field
that closes a cycle is now left null.
* **The generator omitted `new` on a redeclared placeholder.**
`AASOrderedSubmodelElementCollectionType` redeclares the
`SubmodelElement` slot of its base type, and the generated `Add` method
hid the base one without saying so, so the V2 model would not compile.
No existing model could have hit either defect — both are build
failures, so anything that builds today is unaffected.
* **The metamodel half had never worked outside its unit tests**: the
emitted NodeSet declared no reference-type aliases and was rejected by
the loader; the Mandatory `Invoke` Method was never emitted, because a
NodeSet is imported as written rather than instantiated from its type;
the runtime bound at the authored namespace index instead of the one the
Server assigns; and `MethodDeclarationId` was addressed in the wrong
namespace. Every materializer test inspected the in-memory object graph,
which cannot see whether the graph is loadable — two tests now import it
exactly as `RuntimeNodeSetNodeManager` does.
* **The client could not see any Property member.** It browsed
`HasComponent` with subtypes, but `HasProperty` is a sibling under
`Aggregates`, so reading a value failed and invoking failed argument
validation.
* **Every repeated registry write forked a duplicate group and
resource** instead of adding a version, because the identifier was
re-derived against a sibling set that already contained the entity
itself. This also made the persistence bounds unreachable.
* `.gitignore`'s `**/packages/*` silently swallowed the whole
package-integrity implementation and its tests, which would have
vanished on a fresh clone.

## Specification findings

Nine findings against the V3 draft are fixed in
[marcschier/opcua-drafts#75](https://github.com/marcschier/opcua-drafts/pull/75).
The substantive one: clause 6.1.5's absent-versus-empty rule was
**unsatisfiable** for five container fields that materialize as parent
components with no node of their own, which made `AAS-LosslessRoundTrip`
unachievable.

OPC 30270 contradicts itself twice. Both are recorded in
`AasV2ConformanceUnits.cs` and the documentation rather than smoothed
over: Table 83 spells the unit `I4AAS MultiLangaugeProperty` where Table
85 spells it correctly, and Table 85 lists an `I4AAS Security` unit that
Table 83 never defines. Table 84, which would assign profile URIs, is
empty, so `ServerProfileArray` gains no I4AAS entry.

## Known limitation

Calling an `Operation` **with arguments** answers `BadTooManyArguments`.
Method resolution works from both the instance NodeId and the
`MethodDeclarationId`, and the argument definitions are published, but
`NodeState.AddChild` does not route through `MethodState.FindChild`, so
a Method imported from a NodeSet leaves its typed `InputArguments` null.
Closing this means changing how the shared NodeSet import claims typed
children, which affects every consumer of runtime NodeSets and is out of
scope here. It is documented in `docs/Aas.md` §11, the conformance
matrix marks `AAS-OperationInvoke` partial rather than claiming it, and
`samples/Aas` exercises the path and reports the status rather than
working around it. V2 is unaffected — its `Operation` is an
OptionalPlaceholder with no declared arguments.

## Related Issues

- No tracking issue yet. Opening as a **draft** for design discussion
first, per the ADR guidance below; happy to open one and link it once
the shape is agreed.

## 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

| Check | Result |
|---|---|
| AAS tests | **670 passed, 0 failed** on net472, net48, net8.0, net9.0,
net10.0 |
| Coverage | Client 80.8 %, Server 81.2 %, `Opc.Ua.Aas` 82.0 %, WoT 82.4
% — all above the 80 % gate |
| Libraries | `Opc.Ua.Aas`, `.Server`, `.Client`, `.WoT` build clean on
every target framework, zero warnings |
| Regression | `Opc.Ua.Types` 8677, `Opc.Ua.WotCon` 1094,
`Opc.Ua.XRegistry` 198, `Opc.Ua.Server` 4050 — all green |
| Sample | `samples/Aas` builds warning-free and runs end to end |
| Merge validation | Solution builds clean on net10.0 and net48; WoT
Types 504, WotCon 1111, AAS 670, XRegistry 198 tests passed |
| CI | All 99 checks green on `ff9298e44`; only the CLA signature status
is outstanding |

### Review pass

A full review against both specifications, and for API consistency and
correctness, produced twelve
fixes. Each one has a test that fails without it.

| Defect | Where | Why it mattered |
|---|---|---|
| V2 projection aborted on **every** `File` read from a document |
`AasV2EnvironmentRuntime` | The runtime bound `File/Value`
unconditionally, the materializer emitted it only with content, and the
builder throws `BadNotFound` for an unknown NodeId — aborting the whole
registration. No reader ever populates `AasFile.File`, so no AAS V2
document could be hosted at all. |
| `aas-suppl` anchored on the wrong part | `AasxPackageReaderCore`,
`AasxPackageWriter` | IDTA and the Package Explorer anchor it on the
environment part; we used the origin part. Every real `.aasx` returned
zero supplementary files, silently. Affects both halves. |
| Federation failed closed on pooled connections | `AasFederation` |
`ConnectCallback` fires only for new connections, so the second fetch to
an origin — and all of net472/net48/netstandard2.1 — failed with a
security-flavoured error. The peer is now validated *inside* the
callback, which makes reuse safe by construction. |
| Malformed `Decimal` escaped as `ArgumentOutOfRangeException` |
`BinaryDecoder` | A declared body shorter than `Scale` produced a
negative read length. Two of the three test cases had been passing for
the wrong reason. |
| V2 and V3 cross-wired their value providers |
`OpcUaAasServerBuilderExtensions` | Both registered `IAasValueProvider`;
whichever was added first served both AddressSpaces. V2 now has its own
contract, so co-hosting is harmless rather than silently wrong.
`EnvironmentFolder` was also bound and ignored on the V2 path. |
| Export filter skipped three nesting kinds | `AasEnvironmentExporter` |
Content under `Entity`, `AnnotatedRelationshipElement` and `Operation`
was exported unevaluated *and* stamped with a Digest presenting it as
complete (clause 6.5.10). List authorization paths also shifted after a
denial. |
| Concealment held at the Method but not in the projection |
`AasRegistryModel` | `GetSubmodel` answered `BadNotFound` while an
anonymous Browse revealed the node, its identifier, semanticId and
digest (clause 6.5.7). |
| File handles were unsynchronized and unowned | `AasElementFileManager`
| The async node manager holds no lock, so concurrent Calls raced a bare
`Dictionary`; sequential handles let any Session read, seek or commit
another's. Writes were also unbounded. |
| Projection host was unimplementable outside the stack |
`AasEnvironmentProjection` | The handle demanded a type with an internal
constructor — our own tests used
`FormatterServices.GetUninitializedObject` to get round it. Now mirrors
WotCon's interface-typed registration. |
| Embedded `FileType` omitted 7 of 10 Mandatory members |
`AasEnvironmentMaterializer` (V2) | OPC 10000-3 6.4.4 requires them all;
without `Size` and `OpenCount` a Client cannot size a read or detect an
abandoned handle. |
| Round-trip oracle ignored most of the model | `AasRoundTripComparer` |
`Blob`, `File`, `ReferenceElement` and `BasicEventElement` had no case
arm, so their fields compared equal by falling off the switch;
`valueType`, `semanticId`, `entityType` and `Key.type` were never read.
The clause 6.4 losslessness claim rested on it. |

Known, unchanged: calling an `Operation` with arguments answers
`BadTooManyArguments`, because
`NodeState.AddChild` does not route through `MethodState.FindChild`.
Documented in `docs/Aas.md` §11.

Not addressed in this pass, and worth a maintainer decision: OPC 30270
§9.2 says all NodeIds of
Nodes not defined in the companion specification **shall not** use the
standard namespaces, and
Table 87 marks a vendor-specific instance namespace Mandatory. Both
halves currently mint instances
in the companion namespace. Separating them touches NodeId minting, the
runtime and the client
together, so it is better done deliberately than folded into a review
pass.
This is a large change and touches shared code (`Opc.Ua.Types`, the
source generator, the xRegistry projection). It is deliberately a draft:
the enabling changes are separable if maintainers would prefer them
split into their own PRs. The latest `master` is merged into this branch
in `0dd0ff0e1`.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
Copilot-Session: c3d88950-4a65-4ff6-87d2-1f545a875c9b
Copilot-Session: b8ab3763-b9bd-46ae-b0ef-045db26e5782
2026-08-18 08:11:47 -07:00

690 lines
36 KiB
Markdown

# OPC UA for Asset Administration Shell (I4AAS)
This repository implements two **OPC UA for Asset Administration Shell**
metamodel generations in one assembly set. They are separate alternatives, not
revision levels of one model:
* **OPC 30270, "OPC UA for Asset Administration Shell"** maps the AAS V2.0.1
metamodel into namespace `http://opcfoundation.org/UA/I4AAS/` and lives in
`Opc.Ua.Aas.V2`.
* **OPC UA for Asset Administration Shell V3 draft** (release `3.00-draft3`)
maps the AAS V3 draft metamodel into namespace
`http://opcfoundation.org/UA/I4AAS/v3/` and lives in `Opc.Ua.Aas.V3`.
The shared root `Opc.Ua.Aas` namespace contains only code that is genuinely
metamodel-neutral: deterministic NodeId encoding and `idShortPath` handling,
`AasOptional<T>`, digest calculation, materialization diagnostics, the AASX
container walk and the value/operation provider contracts.
| Project | Purpose |
|---------|---------|
| `Opc.Ua.Aas` | Source-generated I4AAS V2 and V3 models from `src/Opc.Ua.Aas/Design`, the V2 object model and ingestion readers, the V3 object model and read/write serializers, shared identity/value helpers, materialization diagnostics and AASX container support. |
| `Opc.Ua.Aas.Server` | Server-side projection for both metamodels over `INodeManagerLifecycle`; V2 ingestion materialization and runtime callbacks; V3 registry service, generational materialization, environment export, federation, package integrity and provider contracts. |
| `Opc.Ua.Aas.Client` | High-level V2 and V3 clients for deterministic metamodel NodeIds, plus the V3 AAS registry client and typed group/resource clients. |
| `Opc.Ua.Aas.WoT` | The V3 informative Annex F bridge between an AAS environment and a WoT Connectivity Thing Description projection. |
| `Opc.Ua.Aas.Tests` | NUnit tests for V2 model ingestion/materialization/server/client behavior and V3 model, identity, values, serialization, registry, materialization, packages, federation, DPP and WoT bridge behavior. |
The V3 AAS implementation builds on the shared [xRegistry](XRegistry.md)
registry base and reuses the runtime NodeManager lifecycle described in
[Runtime NodeSets](RuntimeNodeSets.md). V2 is the OPC 30270 metamodel half only:
it has no registry, packages, federation, DPP or WoT bridge. The IDTA HTTP API
described by Annex G of the V3 draft is intentionally out of scope; this
implementation exposes AAS through OPC UA services and, for V3, the
xRegistry-compatible registry AddressSpace.
---
## Choosing V2 or V3
Host **one** AAS generation in a server: either V2 or V3. The stack does not
enforce that rule at startup, because the two NodeManagers can technically be
registered side by side, but the product decision is that a deployment chooses
one alternative and exposes one metamodel generation. Running both would publish
two different AAS metamodels for the same product domain and leave clients to
guess which one is authoritative.
Choose **V2** when the published OPC 30270 specification and the AAS V2.0.1
metamodel are the interoperability target. The implementation reads V2 JSON, XML
and AASX documents, materializes them into the `http://opcfoundation.org/UA/I4AAS/`
AddressSpace, serves Read + Write + Call, and publishes the OPC 30270 Table 83
conformance units. It does not export the AddressSpace back to AAS documents,
because OPC 30270 does not require a lossless round trip.
Choose **V3** when the draft AAS V3 metamodel and everything built on the draft
registry half are required: registry, updateable materialization, environment
export, packages, package integrity, federation, DPP vocabulary and the WoT
bridge. V3 reads and writes JSON, XML and AASX because its draft requires a
lossless document/AddressSpace round trip.
---
## 1. V3 core architecture
The V3 draft companion specification has two independent halves:
* **Metamodel half** — shells, submodels, concept descriptions and submodel
elements are materialized as typed OPC UA nodes, following clauses 6.1 and
6.2. The metamodel surface is Read + Write + Call: value Variables are served
through an injectable `IAasValueProvider`, and `AASOperationType.Invoke` is
served through an injectable `IAasOperationHandler`.
* **Registry half** — a catalogue of shell, submodel, concept-description,
package and environment documents is served as folders of `FileType` resources
using the xRegistry model (clause 6.5). The registry answers discovery and
document-retrieval questions even when the live metamodel tree is hosted
elsewhere.
Where both halves are hosted, the stored documents are canonical and the live
nodes are derived. A write to a materialized value writes back to the document
and bumps a version; a refresh derives a new generation from the document. The
default retirement policy is `AasProjectionRetirementPolicy.Graceful`, so
existing monitored items can keep reading the old generation until they drain.
The V3 half also adds the OPC UA `Decimal` DataType wire encoding needed
by clause 6.3.1, and the source generator fix needed for recursive structure
DataTypes.
---
## 2. Shared identity and V3 value fidelity
### Deterministic NodeIds and BrowseNames
Clause 6.1.3 assigns deterministic String NodeIds. `AasNodeIdEncoding` is the
public implementation of the reversible, node-kind-discriminated encoding:
```csharp
string shellId = AasNodeIdEncoding.CreateIdentifiableId(
AasNodeKind.Shell,
"https://example.com/aas/42");
string elementId = AasNodeIdEncoding.CreateElementId(
"https://example.com/submodels/nameplate",
"Nameplate.ManufacturerName");
if (AasNodeIdEncoding.TryParse(elementId, out AasParsedNodeId parsed))
{
Console.WriteLine(parsed.IdShortPath);
}
```
`AasIdShortPath` builds and parses the metamodel `idShortPath` convention, and
`AasBrowseNameAllocator` implements the derived BrowseName rule for
identifiables that do not carry `idShort`.
### xsd value mapping
`AasXsdTypeMap` maps every `AASDataTypeDefXsdDataType` value to exactly one OPC
UA DataType, as required by clauses 6.1.2 and 6.3.1. `AasLexicalCanonicalizer`
parses and canonicalizes xsd lexical values, and `AasValueSpaceComparer`
compares values in the XML Schema value space rather than by string equality:
```csharp
ExpandedNodeId dataTypeId = Opc.Ua.Aas.V3.AasXsdTypeMap.ToDataTypeId(
Opc.Ua.Aas.V3.AASDataTypeDefXsdDataType.Decimal);
bool parsed = Opc.Ua.Aas.V3.AasLexicalCanonicalizer.TryParse(
"1.500000",
Opc.Ua.Aas.V3.AASDataTypeDefXsdDataType.Decimal,
out Variant value,
out string? error);
bool equivalent = Opc.Ua.Aas.V3.AasValueSpaceComparer.AreEquivalent(
"1.500000",
"1.5",
Opc.Ua.Aas.V3.AASDataTypeDefXsdDataType.Decimal);
```
The object model uses `AasOptional<T>` so absent remains distinct from
present-but-empty (clause 6.1.5). Serializers and round-trip comparison preserve
that distinction.
---
## 3. V3 reading, writing and materializing AAS documents
V3 AAS JSON, XML and AASX are read into the same object model. JSON and XML readers
return diagnostics instead of throwing for malformed documents:
```csharp
await using FileStream input = File.OpenRead("environment.json");
Opc.Ua.Aas.V3.AasDocumentReadResult read = await new Opc.Ua.Aas.V3.AasJsonReader()
.ReadAsync(input, ct);
if (!read.Succeeded)
{
Console.WriteLine(read.Error);
return;
}
Opc.Ua.Aas.AasMaterializationResult materialized = Opc.Ua.Aas.V3.AasEnvironmentMaterializer
.Materialize(read.Environment!);
await using FileStream output = File.Create("environment.aasx");
await new Opc.Ua.Aas.V3.AasxPackageWriter().WriteAsync(output, read.Environment!, ct);
```
The inverse path serializes a clause 6.1.6 NodeSet back to an AAS environment:
```csharp
Opc.Ua.Aas.V3.AasSerializationResult serialized = Opc.Ua.Aas.V3.AasEnvironmentSerializer
.Serialize(materialized.NodeSet);
await using FileStream json = File.Create("roundtrip.json");
await new Opc.Ua.Aas.V3.AasJsonWriter().WriteAsync(json, serialized.Environment, ct);
```
The round-trip guarantee in clause 6.4 is equivalence, not byte identity. A
canonical value rewrite such as `"1.500000"` to `"1.5"` is equivalent for
`xs:decimal`; losing digits, conflating absent with empty or changing ordered
arrays is not equivalent.
---
## 4. Hosting the V3 metamodel server
`AddAasV3Server` registers the environment NodeManager, default providers and the
runtime projection host. It must be used with the normal server hosting feature:
```csharp
services.AddSingleton<IAasValueProvider, MyAasValueProvider>();
services
.AddOpcUa()
.AddServer(options =>
{
options.EndpointUrls.Add("opc.tcp://localhost:4840/AasServer");
})
.Services
.AddOpcUa()
.AddAasV3Server(options =>
{
options.EnvironmentFolder = "aas-environments";
options.RetirementPolicy = AasProjectionRetirementPolicy.Graceful;
})
.AddOperationHandler<MyAasOperationHandler>();
```
`EnvironmentFolder` is read by `FolderAasEnvironmentProvider`; when it is not
set, `InMemoryAasEnvironmentProvider` is used. The metamodel server can also be
constructed directly with `AasEnvironmentNodeManagerFactory` when an application
manages `StandardServer.NodeManagerFactories` itself.
The runtime uses `IAasValueProvider` for reads and writes of materialized value
Variables:
```csharp
public sealed class MyAasValueProvider : IAasValueProvider
{
public ValueTask<AasValueReadResult> ReadValueAsync(
NodeId valueNodeId,
CancellationToken cancellationToken = default)
{
return new ValueTask<AasValueReadResult>(new AasValueReadResult(
ServiceResult.Good,
Variant.From("running"),
StatusCodes.Good,
DateTime.UtcNow));
}
public ValueTask<ServiceResult> WriteValueAsync(
NodeId valueNodeId,
Variant value,
CancellationToken cancellationToken = default)
{
return new ValueTask<ServiceResult>(ServiceResult.Good);
}
}
```
`IAasOperationHandler.InvokeAsync` receives already-marshalled input and
in-output values and returns the four outputs required by `AASOperationType.Invoke`.
A failed AAS operation is represented as a Good OPC UA Call with `Success = false`
and a diagnostic, which is distinct from a failed Call service result.
---
## 5. Using the V3 metamodel client
`Opc.Ua.Aas.Client.AasClient` computes the deterministic NodeIds of clause 6.1.3, opens the
generated `*TypeClient` proxies, reads values with their declared xsd type and
writes lexical values using that type:
```csharp
var client = new Opc.Ua.Aas.Client.AasClient(session, aasNamespaceIndex, telemetry);
Opc.Ua.Aas.V3.AASPropertyTypeClient temperature = client.OpenProperty(
"https://example.com/submodels/process",
"Temperature");
Opc.Ua.Aas.Client.AasValueReadResult current = await client.ReadValueAsync(temperature.ObjectId, ct);
Console.WriteLine($"{current.ValueType}: {current.LexicalValue}");
StatusCode writeStatus = await client.WriteLexicalValueAsync(
temperature.ObjectId,
"42.5",
ct);
```
Operations keep the OPC UA Call status separate from the AAS success flag:
```csharp
Opc.Ua.Aas.V3.AASOperationTypeClient reset = client.OpenOperation(
"https://example.com/submodels/process",
"Reset");
Opc.Ua.Aas.Client.AasOperationInvokeResult result = await client.InvokeAsync(
reset.ObjectId,
ArrayOf<Variant>.Empty,
ArrayOf<Variant>.Empty,
0,
ct);
if (StatusCode.IsGood(result.CallStatusCode) && !result.Success)
{
Console.WriteLine(result.Diagnostic);
}
```
Dependency injection registers either a factory over an already connected
`ManagedSession`, or a lazy factory that asks the registered OPC UA client for a
session:
```csharp
services
.AddOpcUa()
.AddClient(options => { /* endpoint and application options */ })
.AddAasV3Client(options =>
{
options.InstanceNamespaceUri = "urn:example:aas:instances";
});
Func<CancellationToken, Task<Opc.Ua.Aas.Client.AasClient>> clientFactory =
sp.GetRequiredService<Func<CancellationToken, Task<Opc.Ua.Aas.Client.AasClient>>>();
Opc.Ua.Aas.Client.AasClient aas = await clientFactory(ct);
```
---
## 6. V3 AAS registry
The V3 AAS registry is a concrete xRegistry specialization (clause 6.5). It exposes
the well-known `AASRegistry` object under `Server`, projects shell groups,
submodel files, concept dictionaries, packages and environment documents, and
wires the AAS discovery methods:
* `LookupShellsByAssetLink(Name, Value)` returns matching shell group NodeIds.
* `GetSubmodel(SubmodelIdentifier)` returns the document bytes, `Format` and
`ContentType`, while preserving `Bad_UserAccessDenied` and `Bad_NotFound` as
meaningful outcomes.
The service keeps immutable snapshots. Every mutation advances `Generation`, and
existing readers keep their previous snapshot:
```csharp
var registry = new Opc.Ua.Aas.Server.Registry.AasRegistryService();
await registry.UpsertResourceAsync(new Opc.Ua.Aas.Server.Registry.AasUpsertResourceRequest
{
GroupSourceIdentity = "https://example.com/aas/42",
ResourceSourceIdentity = "https://example.com/submodels/nameplate",
GroupKind = Opc.Ua.Aas.Server.Registry.AasRegistryEntityKind.Shell,
ResourceKind = Opc.Ua.Aas.Server.Registry.AasRegistryEntityKind.Submodel,
Content = ByteString.From(File.ReadAllBytes("nameplate.json")),
ContentType = "application/aas+json",
Format = "aas/3.0+json"
});
Opc.Ua.Aas.Server.Registry.AasGetSubmodelResult document = await registry.GetSubmodelAsync(
"https://example.com/submodels/nameplate");
```
To expose the registry in a manually configured server, add the registry node
manager factory to the server's node manager factories:
```csharp
var registry = new Opc.Ua.Aas.Server.Registry.AasRegistryService();
server.NodeManagerFactories.Add(
new Opc.Ua.Aas.Server.Registry.AasRegistryNodeManagerFactory(registry));
```
The V3 AAS registry projection uses the shared `XRegistryProjectionEngine` from
`Opc.Ua.XRegistry.Server`, so base xRegistry lifecycle, labels and file-transfer
semantics match the WoT registry implementation.
### Registry client
`AasRegistryClient` derives from `XRegistryClient`. It resolves the well-known
root, inherits the base xRegistry lifecycle helpers, and adds the AAS discovery
methods and typed resource clients:
```csharp
Opc.Ua.Aas.Client.Registry.AasRegistryClient registry =
await Opc.Ua.Aas.Client.Registry.AasRegistryClient.ForServerAsync(
session,
telemetry,
ct);
ArrayOf<NodeId> shells = await registry.LookupShellsByAssetLinkAsync(
"serial",
"42",
ct);
Opc.Ua.Aas.Client.Registry.AasGetSubmodelDocumentResult submodel = await registry.GetSubmodelAsync(
"https://example.com/submodels/nameplate",
ct);
if (StatusCode.IsGood(submodel.StatusCode))
{
File.WriteAllBytes("nameplate.json", submodel.Document.ToArray());
}
```
The DI shortcut is `AddAasV3RegistryClient`:
```csharp
services
.AddOpcUa()
.AddClient(options => { /* endpoint and application options */ })
.AddAasV3RegistryClient();
```
---
## 7. V3 updateable registry and environment export
`AasMaterializationCoordinator` implements the optional updateable-registry
profile of clause 6.5.9. It reads `AasMaterializationDocument` entries from an
`IAasMaterializationDocumentStore`, prepares shadow generations, atomically
switches them through an `IAasEnvironmentProjectionHost`, records per-document
outcomes and skips unchanged digests unless `Force` is set.
Important behaviours:
* **Documents are canonical.** Materialized nodes can be rebuilt from the stored
documents and the specification. No extra per-node state is required.
* **Switches are atomic.** Shadow generations are not browsable and do not emit
model-change events until committed.
* **Failures retain the previous generation.** Invalid desired versions stay in
the registry with diagnostics while the previous active generation continues
serving.
* **Graceful retirement is the default.** Existing monitored items survive an
update until retained work drains; `Immediate` invalidates affected monitored
items with `BadNodeIdUnknown`.
* **Value write-back bumps versions.** Writes through materialized nodes update
the stored document and avoid redundant projection when the digest is unchanged.
`AasEnvironmentExporter` implements clause 6.5.10. It exports the whole
materialized environment as AAS JSON, XML or AASX and filters the result per the
calling session's read access. A filtered export does not publish a digest,
because the bytes depend on the caller.
---
## 8. V3 federation, packages, DPP and WoT
### Federation
AAS federation follows clause 6.5.6. `AasFederationIdentity` derives proxy
identity from the remote AAS identifier attributes, never from the local endpoint.
`AasResourceUrlFederationResolver` and `AasOpcUaFederationResolver` apply a
fail-closed egress policy: host/scheme controls, DNS and connected-address
checks, redirect/time/size bounds, no ambient credentials and OPC UA peer
identity validation.
### Packages
`AasPackageIntegrity` and `IAasPackageStore` implement the package integrity
rules in clause 6.5.4. Package versions publish immutable `Digest` and
`DigestAlg`; consumers verify the returned blob by default. OCI-backed packages
verify the manifest digest, require exactly one package layer descriptor, keep
mutable tags separate from immutable `VersionId`s and keep referrers out of the
package version collection.
### Digital Product Passport vocabulary
The `Dpp/` namespace provides a small Digital Product Passport vocabulary layer.
`AasDppDisclosurePolicy` maps DPP regulatory classes to the AAS `Public` and
`Controlled` disclosure tiers, while preserving the finer disclosure class and
authorization text for consumers.
### WoT bridge
`Opc.Ua.Aas.WoT` implements the informative Annex F projection. `AasWotBridge`
reads a bundle of Thing Descriptions carrying the AAS and OPC UA WoT Binding
terms, resolves the type binding (`@type`, `ua:HasTypeDefinition` or both), and
projects the object graph described by Annex F. It does not replace the WoT
Connectivity registry; it is the AAS-specific bridge between the two companion
specifications.
---
## 9. Security and disclosure notes
Registry writes and file updates inherit the xRegistry security model. The AAS
`GetSubmodel` convenience method deliberately re-checks authorization on the
target resource; permission to call the method is never substituted for
permission to read the file. Denied responses return no bytes, no parse metadata
and no size or digest. Concealed targets and nonexistent targets follow the same
externally visible branch.
`DisclosureTier` and `Authorization` are metadata, not credentials. The
implemented DPP policy maps regulatory classes to public or controlled tiers,
and tests assert that `AASAuthorizationOptionDataType` carries configuration
only, not passwords, tokens, keys or secrets.
Federation resolves untrusted metadata and therefore fails closed. A resolver
policy, DNS, redirect, credential, certificate, identity or resource-bound
failure terminates resolution without returning or caching bytes from the
rejected destination.
---
## 10. V3 conformance matrix
The following table maps the sixteen V3 draft clause 10 conformance units to tests in
`tests/Opc.Ua.Aas.Tests`. Each cited test was checked against the behaviour it
asserts; no unit is intentionally left unmapped.
A Server publishes the units it actually enables. Both NodeManagers implement
`IConformanceContributor`, and the server aggregates their contributions into
`Server/ServerCapabilities/ConformanceUnits`, so the published set follows what
was wired up rather than what the assemblies could in principle do:
`AAS-OperationInvoke` appears only when an operation handler other than
`DefaultAasOperationHandler` is configured, and `AAS-Packages` (with
`AAS-PackageIntegrity`, which clause 10 requires alongside it) only when a
package group is present. The unit names are held in `AasConformanceUnits`.
`ServerProfileArray` gains no AAS entry. Clause 10 names conformance units but
assigns no server profile URIs, and the IDTA profile identifier of Annex G is
qualified on also implementing the HTTP binding, which this stack does not — so
publishing it would claim conformance that is not met.
| Conformance unit | Demonstrating tests |
|------------------|---------------------|
| `AAS-Metamodel` | `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsAnnexBMembers`; `Client.AasClientTests.OpenMethodsResolveByIdentifierAndIdShortPath` |
| `AAS-SubmodelElements` | `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsAnnexBMembers`; `Serialization.AasSerializationTests.JsonRoundTripPreservesEverySubmodelElementType` |
| `AAS-ValueFidelity` | `Values.AasXsdTypeMapTests.EveryDeclaredXsdTypeIsAssignedADataType`; `Values.AasXsdTypeMapTests.NoDataTypeIsAssignedToTwoXsdTypes`; `Values.AasLexicalCanonicalizerTests.CanonicalizationIsIdempotent`; `Client.AasClientTests.ReadAndWriteValueUseDeclaredXsdType` |
| `AAS-InstanceMaterialization` | `Materialization.AasEnvironmentMaterializerTests.OrderedListUsesHasOrderedComponentAndUnorderedListUsesHasComponent`; `Materialization.AasEnvironmentMaterializerTests.ListMembersAreNamedByIndexAndCarrySequentialIndex`; `Materialization.AasEnvironmentMaterializerTests.MaterializingTheSameEnvironmentTwiceProducesByteIdenticalNodeSet` |
| `AAS-LosslessRoundTrip` | `Materialization.AasEnvironmentRoundTripTests.MaterializeThenSerializeProducesEquivalentEnvironment`; `Materialization.AasEnvironmentRoundTripTests.SerializeThenMaterializeProducesEquivalentNodeSet`; `Materialization.AasEnvironmentRoundTripTests.ConflatingAbsentWithEmptyIsReported`; `Materialization.AasEnvironmentRoundTripTests.RewritingValueIntoCanonicalLexicalFormIsNotReported` |
| `AAS-Registry` | `Registry.AasRegistryProjectionTests.ProjectionExposesEverySourceIdentityVerbatim`; `Registry.AasRegistryProjectionTests.ConcreteRegistryMethodsCarryDeclarationIds`; `Registry.AasRegistryServiceTests.UpsertResourceAdvancesGenerationAndPreservesExistingSnapshots` |
| `AAS-RegistryIdentity` | `Registry.AasRegistryServiceTests.IdentifierConstructionMatchesXRegistryAndIsInvariantAcrossVersions`; `Registry.AasRegistryProjectionTests.ProjectionExposesEverySourceIdentityVerbatim`; `Client.AasRegistryClientTests.TypedGroupAndResourceClientsExposeSourceIdentities` |
| `AAS-RegistryVersioning` | `Registry.AasRegistryServiceTests.VersionOrderingResolvesNewestVersionNotLaterThanMoment`; `Client.AasRegistryClientTests.VersionsResolveNewestVersionNotLaterThanMoment` |
| `AAS-Discovery` | `Registry.AasRegistryServiceTests.LookupShellsByAssetLinkFindsShellAndBoundsUnauthenticatedResults`; `Registry.AasRegistryGetSubmodelContractTests.AuthorizedCallerGetsDocumentFormatAndContentType`; `Client.AasRegistryClientTests.LookupShellsByAssetLinkReturnsHitAndMiss`; `Client.AasRegistryClientTests.GetSubmodelReturnsDocumentOnSuccess` |
| `AAS-OperationInvoke` (partial, see section 11) | `Server.AasRuntimeInvocationTests.InvokeWithCorrectArityReturnsOperationOutputsAsync`; `Server.AasRuntimeInvocationTests.InvokeWithArityMismatchReturnsBadInvalidArgumentAsync`; `Server.AasRuntimeInvocationTests.InvokeOperationFailureReturnsGoodCallStatusAndFalseSuccessAsync`; `Client.AasClientTests.InvokeMarshalsArguments` |
| `AAS-Federation` | `Federation.AasFederationTests.ProxyIdentityRetainsRemoteAttributesAndIgnoresLocalEndpoint`; `Federation.AasFederationTests.EgressPolicyRejectsRestrictedAddressAndReturnsNoBytes`; `Federation.AasFederationTests.ConnectedAddressRevalidationBlocksDnsRebinding`; `Federation.AasFederationTests.OpcUaPeerIdentityMismatchTerminatesWithoutRemoteRead` |
| `AAS-DisclosureTiers` | `Registry.AasRegistryServiceTests.DisclosureTierAndAuthorizationAdvertiseConfigurationOnly`; `Registry.AasRegistryServiceTests.ControlledDisclosureTierRequiresAuthenticationButPublicDoesNot`; `Registry.AasRegistryGetSubmodelContractTests.TargetRolePermissionsDenialReturnsUserAccessDenied`; `Dpp.AasDppDisclosurePolicyTests.RegulatoryClassesMapToDisclosureTiers` |
| `AAS-UpdateableRegistry` | `Updateable.AasUpdateableRegistryMaterializationTests.ShadowGenerationStaysInvisibleUntilAtomicSwitch`; `Updateable.AasUpdateableRegistryMaterializationTests.GracefulRetirementServesExistingMonitoredItemUntilDrain`; `Updateable.AasUpdateableRegistryMaterializationTests.ValidationFailureKeepsPreviousGenerationAndDivergesVersions`; `Updateable.AasUpdateableRegistryMaterializationTests.ValueWriteBackBumpsVersionWithoutRedundantMaterialization` |
| `AAS-EnvironmentExport` | `Updateable.AasUpdateableRegistryMaterializationTests.EnvironmentExportIsFilteredPerSessionWithNoDigest`; `Serialization.AasxPackageTests.JsonPackageRoundTripsEnvironmentAndSupplementaryFiles`; `Serialization.AasxPackageTests.XmlPackageReadsEnvironment` |
| `AAS-Packages` | `Packaging.AasPackageIntegrityTests.PublishedVersionExposesImmutableDigestAndDigestAlg`; `Packaging.AasPackageIntegrityTests.MovingTagToNewManifestRetainsDistinctVersions`; `Packaging.AasPackageIntegrityTests.ReferrerDoesNotMutatePackageVersionCollectionOrLifecycle`; `Client.AasRegistryClientTests.PackageDownloadVerifiesDigestByDefault` |
| `AAS-PackageIntegrity` | `Packaging.AasPackageIntegrityTests.DigestAlgorithmsAcceptOnlyExactAasSpellings`; `Packaging.AasPackageIntegrityTests.MismatchedBlobIsRejectedBeforeItIsReadable`; `Packaging.AasPackageIntegrityTests.ConsumerSideVerificationCatchesTamperedBlob`; `Packaging.AasPackageIntegrityTests.OciBindingPublishesManifestDigestWithPrefixAndDigestWithoutPrefix`; `Packaging.AasPackageIntegrityTests.OciManifestRequiresExactlyOnePackageLayerDescriptor`; `Client.AasRegistryClientTests.PackageDownloadRejectsTamperedBlob` |
---
## 11. V3 limitations and migration notes
* Annex G / IDTA-01002 Part 2 HTTP APIs are not implemented by this branch.
* **Calling a V3 `Operation` with arguments is incomplete.** A materialized
Operation carries its `Invoke` Method, and a Client resolves it by either its
own NodeId or the `MethodDeclarationId` of `AASOperationType.Invoke`. The
Method also carries `InputArguments` and `OutputArguments` under the standard
BrowseNames. What does not yet work is the Call itself: `NodeState.AddChild`
does not route through `MethodState.FindChild`, so a Method imported from a
NodeSet leaves the typed `InputArguments` property null, and the Server
answers `BadTooManyArguments`. Closing this needs a change to how the shared
NodeSet import claims typed children, which affects every consumer of runtime
NodeSets and is therefore out of scope here. `samples/Aas` exercises the path
and reports the status rather than working around it. This limitation is V3-specific;
the V2 `Operation` Method has no declared arguments and is called with an empty
input list.
* The AAS registry server currently exposes direct construction through
`AasRegistryService` and `AasRegistryNodeManagerFactory`; the client has a DI
helper (`AddAasV3RegistryClient`), while the metamodel server has `AddAasV3Server`.
* This branch adds new AAS packages and the OPC UA `Decimal` DataType. No manual
migration from 1.5.378 is required solely to consume the new AAS libraries;
the general 2.0 migration guidance remains in [Migration Guide](MigrationGuide.md).
---
## 12. OPC 30270 / AAS V2 half
The OPC 30270 half implements the published "OPC UA for Asset Administration
Shell" companion specification for the AAS V2.0.1 metamodel. The static model
is generated into `Opc.Ua.Aas.V2`, its namespace URI is
`http://opcfoundation.org/UA/I4AAS/`, and predefined nodes are loaded with the
generated `AddOpcUaAasV2` extension. The server and client entry points are
`AddAasV2Server` and `AddAasV2Client`.
V2 is ingestion-only. `Opc.Ua.Aas.V2.AasJsonReader`,
`Opc.Ua.Aas.V2.AasXmlReader` and `Opc.Ua.Aas.V2.AasxPackageReader` parse AAS
V2.0.1 documents and AASX packages into the V2 object model; there is no
AddressSpace-to-document serializer and therefore no round-trip guarantee. The
materializer projects the document into an OPC UA NodeSet:
```csharp
await using FileStream input = File.OpenRead("environment.json");
Opc.Ua.Aas.V2.AasDocumentReadResult read = await new Opc.Ua.Aas.V2.AasJsonReader()
.ReadAsync(input, ct);
if (!read.Succeeded)
{
Console.WriteLine(read.Error);
return;
}
Opc.Ua.Aas.AasMaterializationResult materialized = Opc.Ua.Aas.V2.AasEnvironmentMaterializer
.Materialize(read.Environment!);
```
`AddAasV2Server` registers the V2 environment NodeManager, the document-backed
value provider and the shared operation handler contract:
```csharp
services
.AddOpcUa()
.AddServer(options =>
{
options.EndpointUrls.Add("opc.tcp://localhost:4840/AasV2Server");
})
.Services
.AddOpcUa()
.AddAasV2Server(options =>
{
options.ControlNamespaceUri = "urn:example:aas-v2:instances";
})
.AddEnvironmentProvider<MyAasV2EnvironmentProvider>()
.AddOperationHandler<MyAasOperationHandler>();
```
The runtime surface is Read + Write + Call, matching the rest of this stack. A
V2 write reaches `IAasValueProvider.WriteValueAsync` and stops there: there is
no V2 document writer that could persist the change back to JSON, XML or AASX.
`Operation` is an `OptionalPlaceholder` Method named `Operation` with no
declared arguments, unlike the V3 mandatory `Invoke` Method, so a V2 Call is
zero-argument.
File and Blob content on V2 is served through an embedded standard OPC UA
`FileType` object. Clients call `Open`, `Read` and `Close` on that child rather
than reading a V3-style value projection:
```csharp
var client = new Opc.Ua.Aas.Client.V2.AasClient(session, aasNamespaceIndex, telemetry);
Opc.Ua.Aas.V2.AASFileTypeClient manual = client.OpenFile(
"https://example.com/submodels/nameplate",
"Manual");
ByteString content = await client.ReadFileContentAsync(manual.ObjectId, ct: ct);
```
OPC 30270 leaves instance NodeIds server-specific and defines no addressing
convention. The V2 materializer and client deliberately reuse the deterministic
`AasNodeIdEncoding` and `AasIdShortPath` rules used by the V3 draft, so one
addressing scheme serves both generations and neither client has to browse for
known identifiers:
```csharp
var client = new Opc.Ua.Aas.Client.V2.AasClient(session, aasNamespaceIndex, telemetry);
Opc.Ua.Aas.V2.AASPropertyTypeClient temperature = client.OpenProperty(
"https://example.com/submodels/process",
"Temperature");
Opc.Ua.Aas.Client.V2.AasValueReadResult current = await client.ReadValueAsync(
temperature.ObjectId,
ct);
StatusCode status = await client.WriteValueAsync(
temperature.ObjectId,
Opc.Ua.Aas.V2.AASValueTypeDataType.Double,
new Variant(42.5),
ct);
```
V2 has no registry, no packages, no federation and no DPP. Those are V3 draft
additions, and the V2 implementation intentionally stops at the OPC 30270
metamodel.
### OPC 30270 conformance units
The V2 server publishes the seventeen conformance units from OPC 30270 Table 83
through `Server/ServerCapabilities/ConformanceUnits` and publishes no
`ServerProfileArray` entry, because Table 84, which would assign profile URIs,
is empty. Two internal specification contradictions are resolved in
`src/Opc.Ua.Aas.Server/V2/AasV2ConformanceUnits.cs`: Table 83 misspells the
multi-language unit as `I4AAS MultiLangaugeProperty` while Table 85 spells it
`I4AAS MultiLanguageProperty`, and Table 85 lists an `I4AAS Security` unit that
Table 83 never defines. The implementation publishes the corrected
`I4AAS MultiLanguageProperty` spelling and does not publish `I4AAS Security`.
Each mapping below was checked against the cited test in
`tests/Opc.Ua.Aas.Tests/V2/`. No OPC 30270 Table 83 unit is currently unmapped.
| OPC 30270 Table 83 unit | Demonstrating tests |
|-------------------------|---------------------|
| `I4AAS AAS` | `Model.AasV2ObjectModelTests.ConstructionRetainsV2TopLevelIdentifiablesAndReferences`; `Serialization.AasV2SerializationTests.JsonReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Server.AasV2ServerTests.ConformanceUnitsPublishOpc30270FacetWithoutProfileUris` |
| `I4AAS Asset` | `Model.AasV2ObjectModelTests.ConstructionRetainsV2TopLevelIdentifiablesAndReferences`; `Model.AasV2ObjectModelTests.V2OnlyConceptsRoundTripThroughModel`; `Client.AasClientTests.OpenMethodsResolveByIdentifierAndIdShortPath` |
| `I4AAS Submodel` | `Model.AasV2ObjectModelTests.ConstructionRetainsV2TopLevelIdentifiablesAndReferences`; `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsMembers`; `Client.AasClientTests.OpenMethodsResolveByIdentifierAndIdShortPath` |
| `I4AAS ConceptDescription` | `Model.AasV2ObjectModelTests.ConceptDescriptionFlavoursRetainIdentifierKinds`; `Serialization.AasV2SerializationTests.XmlReaderParsesV2EnvironmentAndEverySubmodelElementType` |
| `I4AAS View` | `Model.AasV2ObjectModelTests.V2OnlyConceptsRoundTripThroughModel`; `Materialization.AasEnvironmentMaterializerTests.ShellMaterializesAssetViewsInterfacesAndAasReferences` |
| `I4AAS RelationshipElement` | `Serialization.AasV2SerializationTests.JsonReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Serialization.AasV2SerializationTests.XmlReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsMembers` |
| `I4AAS Property` | `Model.AasV2ObjectModelTests.SubmodelElementMembersRetainNodeSetTypes`; `Client.AasClientTests.ReadAndWriteValueUseDeclaredAasValueTypeAsync`; `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsMembers` |
| `I4AAS MultiLanguageProperty` | `Model.AasV2ObjectModelTests.SubmodelElementMembersRetainNodeSetTypes`; `Serialization.AasV2SerializationTests.JsonReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsMembers` |
| `I4AAS Range` | `Serialization.AasV2SerializationTests.JsonReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Serialization.AasV2SerializationTests.XmlReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsMembers` |
| `I4AAS Blob` | `Serialization.AasV2SerializationTests.JsonReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsMembers`; `Client.AasClientTests.FileAndBlobContentAreReadThroughEmbeddedFileTypeAsync` |
| `I4AAS File` | `Serialization.AasV2SerializationTests.AasxReaderReadsXmlJsonAndSupplementaryFiles`; `Server.AasV2ServerTests.EmbeddedFileOpenReadAndCloseServeBlobContentAsync`; `Client.AasClientTests.FileAndBlobContentAreReadThroughEmbeddedFileTypeAsync` |
| `I4AAS ReferenceElement` | `Serialization.AasV2SerializationTests.JsonReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Serialization.AasV2SerializationTests.XmlReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsMembers` |
| `I4AAS Capability` | `Serialization.AasV2SerializationTests.JsonReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Serialization.AasV2SerializationTests.XmlReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsMembers` |
| `I4AAS SubmodelElementCollection` | `Model.AasV2ObjectModelTests.V2OnlyConceptsRoundTripThroughModel`; `Materialization.AasEnvironmentMaterializerTests.OrderedCollectionUsesHasOrderedComponentAndUnorderedCollectionUsesHasComponent`; `AasV2ModelTests.OrderedCollectionRedeclaresTheSubmodelElementPlaceholder` |
| `I4AAS Operation` | `Materialization.AasEnvironmentMaterializerTests.MaterializedNodeSetImportsIntoAnAddressSpace`; `Server.AasV2ServerTests.OperationMethodInvokesHandlerAndRejectsArgumentsAsync`; `Client.AasClientTests.InvokeCallsTheEmbeddedOperationMethodAsync` |
| `I4AAS Event` | `Serialization.AasV2SerializationTests.JsonReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Serialization.AasV2SerializationTests.XmlReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsMembers` |
| `I4AAS Entity` | `Model.AasV2ObjectModelTests.SubmodelElementMembersRetainNodeSetTypes`; `Serialization.AasV2SerializationTests.JsonReaderParsesV2EnvironmentAndEverySubmodelElementType`; `Materialization.AasEnvironmentMaterializerTests.EverySubmodelElementTypeMaterializesWithItsMembers` |
---
## 13. References
* OPC 30270, "OPC UA for Asset Administration Shell", namespace `http://opcfoundation.org/UA/I4AAS/`.
* OPC UA for Asset Administration Shell V3 draft, release `3.00-draft3`, namespace `http://opcfoundation.org/UA/I4AAS/v3/`.
* [xRegistry — abstract registry base model](XRegistry.md).
* [OPC UA WoT Connectivity](WoTConnectivity.md).
* [Dependency Injection](DependencyInjection.md).
* [Runtime NodeSets](RuntimeNodeSets.md).