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.
| Severity | Meaning |
|---|---|
E | Error. The document is rejected. |
W | Warning. Printed, and suppressible per code. |
I | Info. 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.
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.
SomeObject → SomeObjectType. 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
typeDefinitionto 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-012 — modellingRule 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-008 — optionals: 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-009 — dataType 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-004 — inverseName 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-003 — inverseName 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-HEAD — kind: 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-NEXTID — nextId 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.