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-E-NAMING-001 — a browse name that is not an identifier​

OPC UA Part 3 v1.05 §6.4.4.4.4 recommends angle brackets on a placeholder

A browse name chosen by the model must be letters, digits and underscores, starting with a letter; a placeholder is written <Name>, alone or inside a name. OPC UA itself accepts any string, but a name with spaces, slashes or # cannot be written in a browse path, a generated symbol or most client code.

- browseName: <User defined Input#> # ✗
- browseName: <UserDefinedInput> # ✓
- browseName: Port<n> # ✓ a numbered member

A name with a namespace prefix (di:Manufacturer, ua:<OrderedObject>) belongs to another specification and is not checked. When a published NodeSet is projected as it is (tolerant mode, spec-repo pull), the finding is a warning, written as a comment above its line.

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.

Raised only when the add-in type belongs to your model, where you can fix it. Attaching a type from another namespace that you cannot change — ua:FolderType, as OPC 40001-1 Machinery does for MachineryBuildingBlocks — with an explicit browseName: is reported as information instead.

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-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-E-STRUCT-013 — a Method that nothing owns​

OPC UA Part 3 v1.05 §5.7.1

A Method declared under organizes:. A Method shall be the target of at least one HasComponent from an Object or ObjectType; a folder may organize a Method, but only one that some node owns. Declare it where it belongs, and organize it with a reference:

objectTypes:
- browseName: MyControlFunctionType
components:
- browseName: Operational
typeDefinition: di:FunctionalGroupType
organizes:
- browseName: Stop # ✗ a Method with no owner
nodeClass: Method
references: # ✓ the state machine's own Stop
- referenceType: ua:Organizes
source: /Operational
target: /ControlFunctionState/Stop

The target must exist in the type: a method the state machine only inherits from its own type is not a node of this one, so redeclare it (under the state machine member's methods:) before organizing it.

The reverse writes this form only when the NodeSet itself declares such a Method, as OPC 30500 LADS 1.0.0 does for six of them: the finding is then about the published NodeSet. When such a NodeSet is projected as it is (tolerant mode, spec-repo pull), the finding is a warning, written as a comment above its line, and the Method is built as a component of the folder.

DSL-W-STRUCT-014 — an instance with no parent​

OPC UA Part 3 v1.05 §5.2.1

An instances: entry with none of organizedBy, componentOf or propertyOf. It is built as declared, but no hierarchical reference reaches it: a client browsing from the Objects folder never finds it. Give it a parent:

instances:
- browseName: Configuration
typeDefinition: di:FunctionalGroupType
organizedBy: /ua:Objects # ✓ reachable

The reverse writes this form only when the NodeSet declares such a node, as OPC 10000-100 DI does for its design-tool FunctionalGroups (Configuration, Tuning, …): leaving it out would renumber it, and its NodeId is part of the companion specification.

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:
- browseName: 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.

Keys and lists​

These are about the modelling language itself rather than OPC UA. Each one catches something that would otherwise be skipped without a word, leaving a model that builds but is not the one you wrote.

DSL-E-KEY-001 — unknown key on a member or an entry​

A component, property, method or other member, or an entry of a section such as objectTypes: or instances:, carries a key it does not accept. The key is never read, so whatever it was meant to set keeps its default:

components:
- browseName: Pressure
dataType: ua:Double
modelingRule: Optional # ✗ one L — the member stays Mandatory
modellingRule: Optional # ✓
objectTypes:
- browseName: PressBaseType
isAbstact: true # ✗ one R missing — the type stays concrete
isAbstract: true # ✓

When the key is close to a real one the message names it (do you mean modellingRule instead ?). When nothing is close it says so rather than guess. An objects: entry is created as written, so an instance key such as dataType or optionals is reported there with a pointer to instances:.

An ObjectType or VariableType entry with a nested subtypes: list gets the same error. A subtype is always its own entry naming its parent with subtypeOf:

objectTypes:
- browseName: MachineType
- browseName: MillType
subtypeOf: MachineType # ✓ not `subtypes:` under MachineType

DSL-W-KEY-002 — unknown top-level key​

A key at the root of the document that the model does not define, most often a misspelt section name such as objectType:. Nothing reads it, so every entry under it is missing from the generated NodeSet. It is a warning because a stray key is harmless in itself; under --strict it is an error.

DSL-E-KEY-003 — a list was expected​

A section (objectTypes, dataTypes, instances, …) or a member list (components, properties, methods, …) is a mapping or a single value. Each holds a YAML list, one item per node, named by its browseName:

objectTypes:
HydraulicPressType: # ✗ keyed by name: none of it is read
subtypeOf: di:DeviceType

objectTypes:
- browseName: HydraulicPressType # ✓
subtypeOf: di:DeviceType

Until this diagnostic existed the first form produced no output and no message: a model written that way validated as correct while defining nothing.

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.