Skip to main content

Diagnostics

Every conformance diagnostic carries a stable code of the form DSL-<severity>-<area>-<number>. Search for the code — that is what it is for.

SeverityMeaning
EError. The document is rejected.
WWarning. Printed, and suppressible per code.
IInfo. Printed only with --style.

--strict promotes every warning to an error, which is what you want in CI. A model can also escalate specific codes itself through documentation.options.lint.error, or silence them through documentation.options.lint.silence — see Conformance flags.

This page lists what the compiler emits today

Codes are added as rules are implemented. If you meet a code that is not here, it is a bug in this page — please tell us. A CI check keeps the two in step, so it should not happen.

Engineering units

Units are validated against UNECE Recommendation 20, the same list the find_engineering_unit tool searches.

DSL-E-UNIT-001 — unknown unit

The symbol is not a UNECE Rec. 20 unit. The message suggests the closest known one.

engineeringUnits: "degree Celsius" # ✗ prose, not a symbol
engineeringUnits: "°C" # ✓

This is an error: an unknown unit would be written into the NodeSet2 as a unit no client can interpret.

DSL-W-UNIT-002 — ambiguous symbol

Several UNECE units share the symbol you wrote — V, MW and r/min are all ambiguous. The compiler resolves to the first deterministically and tells you which; the message lists the long names so you can be explicit.

DSL-W-UNIT-003 — near match

Not an exact symbol, but close enough to one that the compiler will use it. Write the exact symbol to silence this.

DSL-W-UNIT-004 — US spelling

You wrote a US spelling (meter); UNECE uses the international one (metre). The compiler uses the UNECE spelling.

Devices (DI) placement

The Devices companion specification is prescriptive about where things live on a di:ComponentType subtype. These fire only when your type derives from DI, and they are the most common finding in a first model.

DSL-W-DI-001 — process value outside ParameterSet

OPC 10000-100 (DI) v1.04 §4.3 — "the Parameters are components of the ParameterSet as a flat list of Parameters."

A Variable is declared directly on the type instead of inside the overridden di:ParameterSet.

- browseName: MySensorType
subtypeOf: di:ComponentType
components:
- browseName: Temperature # ✗ top-level process value
dataType: ua:Double
- browseName: MySensorType
subtypeOf: di:ComponentType
components:
- browseName: di:ParameterSet # ✓ redeclared, making it mandatory
components:
- browseName: Temperature
dataType: ua:Double

di:ParameterSet is optional on the base type; redeclaring it here is what makes it mandatory on every instance.

DSL-W-DI-002 — method outside MethodSet

The same rule for Methods: on a di:ComponentType subtype they belong inside the overridden di:MethodSet, not at the type's top level.

Naming

DSL-W-NAMING-003 — type browse name without the Type suffix

Model-Best v1.03 §2.1.3 — "The BrowseName of a TypeDefinitionNode should have the suffix Type."

An ObjectType or VariableType whose browse name does not end in Type. The suffix is what lets a reader tell a type from an instance at a glance, and it is near-universal in the published companion specifications.

SomeObjectSomeObjectType. Interface types are not exempt: the convention there is I…Type, as in IVendorNameplateType.

DSL-W-NAMING-005 — Structure DataType suffix

Model-Best v1.03 §2.1.3 — "For Structured DataTypes (subtypes of the Structure DataType) the suffix should be DataType."

ThreeDVector should be ThreeDVectorDataType.

Structure

DSL-W-STRUCT-006 — AddIn without a default browse name

OPC UA Part 3 v1.05 §4.10.3 — "The AddIn ObjectType shall include the definition of a default BrowseName using the DefaultInstanceBrowseName Property."

An AddIn ObjectType should declare DefaultInstanceBrowseName so instances can adopt it rather than each inventing a name.

DSL-W-STRUCT-009 — inherited member restated

OPC UA Part 3 v1.05 §6.4.4

A member inherited from the parent's typeDefinition restates its nodeClass or its typeDefinition identically. It adds nothing and is ignored — only a redeclaration that changes something has an effect.

Remove the line. If you meant to narrow the type, give a typeDefinition that is a subtype of the inherited one.

DSL-E-STRUCT-010 — inherited member contradicted

OPC UA Part 3 v1.05 §6.4.4

A subtype may refine an inherited member, not contradict it. This is raised when the redeclaration changes:

  • the nodeClass — it is fixed by the declaration you inherit from;
  • the typeDefinition to something that is not a subtype of the inherited one;
  • a Mandatory member's modelling rule to something weaker.

Remove the conflicting key, or make the typeDefinition a subtype of the one you inherit.

DSL-W-STRUCT-011 — browse path does not resolve

OPC UA Part 4 v1.05 §5.8.4

A browse path could not be followed from its starting node — one of its steps matches nothing.

The usual cause is an Optional member that was never activated: an optional member only exists once something asks for it, so a path through one that was not listed in optionals: has nothing to traverse. Check each step of the path, and that every optional step on it is actually materialised.

DSL-W-STRUCT-012modellingRule where there is nothing to govern

OPC UA Part 3 v1.05 §6.4.4

modellingRule on a node whose parent is not an ObjectType or VariableType. A ModellingRule governs an InstanceDeclaration, which only exists under a type; on a member of an ordinary Object or Variable there is none, so the key is ignored.

Remove it — or move the member under the type, if it was meant to be part of the type's definition.

DSL-W-NS-005 — member shadows an inherited one

OPC UA Part 3 v1.05 §4.1

A declared member has the same name as an inherited member but a different namespace, so both exist side by side. A BrowseName is qualified by its namespace, which makes this legal — and easy to miss, because the two read identically unless you look at the prefix.

If you meant to refine the inherited member, use its namespace prefix. If you meant a genuinely new member, a distinct name will save the next reader.

DSL-W-STRUCT-007 — Property reachable only through a Folder

OPC UA Part 3 v1.05 §4.4.2 — "Properties are defined using the HasProperty Reference."

A Property is reachable only by an Organizes reference from a Folder, with no HasProperty from the Node it belongs to. A Folder may organize a Property it does not own, but the owning Node still has to declare it — otherwise a client browsing the Node never sees the Property.

DSL-W-STRUCT-008optionals: on a type

OPC UA Part 3 v1.05 §6.4.4

optionals: on a type has no effect and was discarded. A type's members are inherited from its supertype already carrying their own modelling rule; optionals: only means something where a typeDefinition is instantiated. To make an inherited member Mandatory on a type, use promotedToMandatory:.

Reference types

DSL-E-DT-009dataType where typeDefinition belongs

OPC UA Part 3 v1.05 §4.4

dataType: names something that resolves to an ObjectType. A Variable has a DataType; an Object has a TypeDefinition. These are the two being confused, and the node would end up with neither.

components:
MyComponent:
dataType: MyObjectType # ✗
typeDefinition: MyObjectType # ✓

DSL-W-REF-004inverseName omitted

OPC UA Part 3 v1.05 §5.3.3

An asymmetric ReferenceType named Has<X> declared no inverseName, so Is<X>Of was derived and used. The model still builds, but the generated NodeSet carries a name nobody chose — write it explicitly if the derived one is not what you mean.

DSL-W-REF-003inverseName differs from the convention

OPC UA Part 3 v1.05 §5.3.3

A Has<X> ReferenceType whose inverseName is not Is<X>Of. The pairing is the convention every standard ReferenceType follows, so departing from it makes the relationship read as something other than it is. Deliberate departures are fine — this is a warning, not an error.

Model file form

These describe the shape of the file, not the model in it. A model can be perfectly valid and still draw one.

DSL-W-FORM-KIND-IN-HEADkind: in the model document

kind: opens a tail document of a self-contained stream; document 1 is the model and is marked profile: self-contained instead. A stray kind: in the head flips generate onto the stream path with no table to read.

Put a --- separator before the line so it opens a tail document, delete it, or let opcua-modeler pack write the tail.

DSL-W-FORM-NOT-SEALED.opcua.yaml with no sealed table

The file carries the self-contained extension but holds no kind: symbols document, so NodeIds are still read from and written to the .csv sidecar rather than the file itself.

Run opcua-modeler pack to seal the table in, or rename it back to *.model.yaml to keep the pair form.

DSL-I-FORM-MISNAMED — sealed stream not named .opcua.yaml

The file is a sealed self-contained stream but is not named *.opcua.yaml, so editors bind the single-document model schema to it and flag every line of a perfectly correct tail.

DSL-W-FORM-ORPHAN-CSV — sidecar beside a sealed stream

A .csv symbol file sits next to a sealed stream. The embedded table is the only ledger that is read; the CSV is ignored and will drift out of date.

Doctor — file and ledger health

opcua-modeler doctor reports these. They are about NodeId stability across regenerations, which is what keeps a published NodeSet's ids meaningful.

DSL-E-DOCTOR-MISSING — file does not exist

A file named on the command line is not there.

DSL-E-DOCTOR-STREAM-PARSE — stream does not parse

The file looks like a self-contained stream but does not parse. Check the --- separators and the kind: key of each tail document.

DSL-W-DOCTOR-NO-OWN-TABLE — no table for its own namespace

The file is a stream, but none of its kind: symbols documents is for its own namespaceUri — so every NodeId is allocated afresh on the next generate.

DSL-W-DOCTOR-LEDGER-STALE — table sealed against an older model

Nodes added since have no id yet; nodes removed since are still listed. Not fatal — generate re-seals — but worth knowing before a release rather than after.

DSL-E-DOCTOR-DUPLICATE-ID — one id, two nodes

The same NodeId is assigned to two different browse names. Give one of them a fresh id by hand, regenerate, and compare the NodeSet2.xml.

DSL-E-DOCTOR-RECYCLED-ID — a retired id came back

An id is both retired and live. Retirement exists precisely to stop this: a client that cached the old node now resolves it to a different one. Move the live node to a fresh id above nextId.

DSL-E-DOCTOR-NEXTIDnextId is already in use

nextId is at or below an id that is already assigned or retired, so the next node added would collide. Raise it above the highest id in use.

DSL-I-DOCTOR-NO-LEDGER — no symbol file

A pair-form model with no .csv. Every NodeId is allocated from scratch on the next generate — harmless the first time, a renumbering of a published NodeSet every time after. Generate once and keep the ledger under version control, or pack to the self-contained form.

DSL-W-DOCTOR-OUTPUT-STALE — NodeSet older than the model

The generated .NodeSet2.xml is older than the model it came from, so it does not describe the current model.

Dependency versions

These two report on the companion specifications you import, and are not conformance rules — nothing in your model is wrong.

DSL-W-DEP-VERSION — major version differs

A nodeset was built against a different major version of a dependency than the one being loaded. Worth reading: a major bump can move or remove types.

DSL-I-DEP-VERSION — newer, same major

A dependency is newer than the one the nodeset was built against, within the same major version. Informational, and the normal state of affairs.

Silencing a diagnostic

Per code, or by prefix:

opcua-modeler generate -i model.yaml --silence DSL-W-NAMING-005,DSL-W-UNIT-

or in the model itself, so the decision travels with it:

documentation:
options:
lint:
silence: [DSL-W-NAMING-005]

Prefer fixing to silencing. A silenced warning is a decision, so leave a comment saying why — the next reader cannot see what you knew.