Pitfalls
The mistakes definition authors actually make, why they happen, and what to do instead.
Most of them are caught by cfu def validate; this chapter explains the diagnostics you will see.
Root Element Repeats the Definition Name
A definition named Indicator with a root property indicator derives the type Indicator::Indicator.
Two types now share the simple name Indicator, which clashes in generated code:
error: Multiple types which require a generated class/struct use the same simple name Indicator. This will clash in the generated sources.
Either name the definition differently from its root element (definition TurnIndicator, root indicator) or give one of them an explicit typeName.
Unanchored Regular Expressions
pattern follows JSON Schema semantics and matches anywhere in the string: ` accepts `abc123`.
Anchor your patterns: `^[0-9]$.
Singular and Plural Property Names
A list entity or struct (upper multiplicity above one) must have a plural name and a single valued property a singular one: sensors with 0..*, sensor with 0..1.
A list parameter may use either form.
The validator errors on mismatch.
If the natural name violates the rule (for example a word that has no distinct plural), set allowPropertyNameDeviation: true on the element rather than bending the name.
Words ending in Class, Address, Status, Bytes, or Flags are exempt and fit a list and a single element alike.
A list entity or struct, or a list with a plural name, must also carry an explicit typeName giving the singular type name.
sensors with 0..* needs typeName: "Sensor".
The validator errors when a list has no typeName, and allowPropertyNameDeviation does not suppress this requirement.
Verb Prefixes in Property Names
Names must not start or end with enable/enabled/disable/disabled.
enabledLogging reads as a command, not a property.
Model the fact instead: logging: { bool: … } or an enum with meaningful states.
This rule keeps configuration files declarative.
Reserved Property Names
id, name, $type, $comment, and $description are implicit on every entity and cannot be declared as your own properties.
Pick another name (shortName, label, kind).
Enum Where a String Belongs, and Vice Versa
Enum literals must be identifiers and become enum constants in generated code.
If the values are not identifiers ("front-left") or are open ended, use a string with literalsExhaustive (closed set) or literalsNonExhaustive (suggestions).
If the set is closed and the software switches over it, use enum.
Untagged Unions
allowTypeAmbiguity: true removes the $type discriminator.
A value that structurally matches two members is then ambiguous, and different consumers (Rust serde, Python pydantic, the validator) may pick different members.
Keep unions discriminated unless you control every consumer and the members cannot overlap.
It is also the only way to include scalar (parameter) or ref members: a discriminated union rejects them, because they cannot carry a $type discriminator.
Same Parameter Set in Two Merged Files
In MultipleFilesEntitiesMergeable mode all files have equal priority, so two files setting different values for the same parameter of the same entity is a merge conflict, and the
same value twice is a maintenance trap.
Give each parameter one owning file.
If you need layered overrides, that is what MultipleFilesEntitiesMergeableWithPriority and $fileType are for.
Reference Paths Name Entities, Not Relations
A reference value like /SensorEcu/FrontBodyEcu/OilTemp is built from the configured entity identities (name, else id), starting at the definition name.
It does not contain the definition’s property names (ecu, sensors), and it cannot address an entity that has neither name nor id.
If something must be referencable, make sure configuration authors give it an identity.
f64 Is the Only Float
There is no f32.
If the target uses a 32 bit float, constrain min/max accordingly and state the real type in embeddedTypeHint; the generated bindings use f64, and the range or precision
mismatch is not checked.
The same applies generally: embeddedTypeHint is documentation, not semantics.
Renames Are Breaking Changes
There is no alias mechanism.
Renaming a property, a type with typeName, or an enum literal invalidates every existing configuration and every consumer of the generated code at once.
Get names right early (they are checked against the naming rules for a reason), and when you must rename, bump the major of definitionVersion and coordinate the migration.
A Source Value Is an Identity, Not Just a Value
An entity declaring nameSource or idSource is named by that parameter, so editing the value rewrites every reference path and every fragment $path running through the entity.
Three things follow from the name being built per file, before mixins and before the .default / .preconfig merge:
-
A
$pathfragment has to spell the mangled name (/Bus/comvectorEngine), not the value it was mangled from. -
A mergeable contribution has to repeat the source value, since each file builds the name it merges under on its own.
-
A mixin body cannot carry an
idSourceentity, for the same reason it cannot carry anid: one body applies to many elements, while an id is unique across the configuration. A value arriving through a mixin is also too late to name anything.
A Passing Validation on an Undiscovered File
cfu cfg validate can only validate what workspace discovery found.
A config outside the workspace root, behind an ignoreTraversal marker, or in a hidden directory is silently absent.
When in doubt, run cfu workspace list and check the file appears; a validation that never saw your file is not a green light.