Change one line, watch every artifact move
In a public architecture thread about a software-defined-vehicle reference platform, someone building its diagnostics integration asked, almost word for word: what should be the source of truth for DID, DTC, routine, security and transport definitions? It's the right question, and it has a concrete answer rather than an abstract one. This post is the concrete version.
A single YAML file, and here is what happens when one field in it changes.
Why this is the question that matters
Every UDS-capable ECU has the same definitions living in at least four places: the firmware that implements them, the test suite that exercises them, the tool-facing description a tester or a gateway reads, and — if you're doing this by hand — whatever document someone wrote down first. Those four things drift. Not from carelessness; from time. A DID gets a byte added for a new revision, three of the four places get updated, and the fourth is the one nobody remembers to check until a field unit returns a response one byte longer than the test expects.
We don't get a pass on this ourselves just because we write the generator. Porting three independent, MIT-licensed conformance-vector suites into EDS this cycle — ISO 15765-2, ISO 13400-2, ISO 14229-1 — found real protocol defects in our own DoIP implementation on first contact (#369, #371, #368). Drift is real, and the honest response to it is not a promise to be more careful. It's a structure where drift can't happen because there's only one place to change.
The one-line change
Take examples/ardep_ecu/diagnostics_config.yaml in EDS — a real example, not a toy, shaped around a Mercedes-Benz ARDEP board. DID 0xF187, VehicleManufacturerSparePartNumber, a standard ISO 14229-1 Annex F identifier. One field:
data_length: 11 # Supplier part number
becomes
data_length: 14 # Supplier part number
Regenerate everything that config drives, and diff before against after.
What actually moves
The C handler — buffer size, length check, and every doc comment that quotes the length, all in one file:
-/** Stub backing store for DID 0xF187 — VehicleManufacturerSparePartNumber (11 byte(s)). */
+/** Stub backing store for DID 0xF187 — VehicleManufacturerSparePartNumber (14 byte(s)). */
-static uint8_t s_mock_vehiclemanufacturersparepartnumber[11U] = { 0U };
+static uint8_t s_mock_vehiclemanufacturersparepartnumber[14U] = { 0U };
...
- * DATA LENGTH : 11 byte(s)
+ * DATA LENGTH : 14 byte(s)
...
- * @param[in] buf_len Capacity of buf in bytes. Must be >= 11.
+ * @param[in] buf_len Capacity of buf in bytes. Must be >= 14.
The SOVD CDA descriptor — the tool-facing description of this ECU's diagnostic surface — moves the same field by the same amount:
{
"id": "0xF187",
"name": "VehicleManufacturerSparePartNumber",
- "dataLengthBytes": 11,
+ "dataLengthBytes": 14,
"access": ["read", "write"],
"minSession": "extended",
"readSecurityLevel": 0,
"writeSecurityLevel": 1
}
The CANoe CAPL suite — PDU-length arithmetic that nobody hand-maintains, because nobody hand-wrote it:
- /* Response: 0x62 DID_HI DID_LO + 11 data bytes = 14 total */ + /* Response: 0x62 DID_HI DID_LO + 14 data bytes = 17 total */ snprintf(stepName, elcount(stepName), "RDBI_0xF187_ResponseLength"); - AssertResponseLength(gRxLen, 14, stepName); + AssertResponseLength(gRxLen, 17, stepName);
The pytest suite — including the NRC boundary tests, which retarget themselves: "write 10 bytes, expect NRC 0x13" becomes "write 13 bytes, expect NRC 0x13," because the wrong length to test is itself derived from the right one.
Eleven files. Every surviving line in the diff traces back to 11 → 14 or its direct arithmetic (14, 17, 13). Nothing else moves.
Two things worth being precise about
This claim is easy to overstate in a way that sounds almost identical but means something different, and this is exactly the audience that would catch it.
We generate to these four formats from a definition we own — not from someone else's schema. That distinction mattered concretely when the Eclipse opensovd-core team abandoned generating their REST skeleton from the SOVD OpenAPI schema and hand-wrote it instead, because the schema's quality and versioning were outside their control. "Generate from a single source of truth" is the phrase that burned them. Said carelessly, this demo sounds like the thing they just walked away from. It isn't — the direction is reversed. The restricted artifact, if there is one, is an optional export target downstream of a definition we control, not a required input upstream of it.
sovd_cda.json is EDS's own CDA configuration descriptor — not an ASAM SOVD or ISO 17978 artifact. We used to imply otherwise (the field was literally named sovdVersion). Checking it against the real ISO 17978-3 OpenAPI specification found that SOVD's own "capability description" is a dynamically-served, per-resource OpenAPI document, not a static flat JSON file — a different kind of artifact, not just a different version. We corrected it ourselves before anyone outside caught it: the field is now edsCdaSchemaVersion, and every "OpenSOVD 1.0" claim in our own docs is gone.
Where the evidence stops
Stated plainly, same as always.
Not every reader can reproduce all four outputs. The C sources and the SOVD CDA descriptor come from the public, free tools/codegen.py — anyone can run that. The pytest suite and the CAPL suite come from tools/testgen.py, which ships with the Developer and Professional tiers, not the free one. The script below checks which one it has and tells you honestly which half it ran.
This answers one question, not the architecture debate. It shows a concrete candidate for "what should be the source of truth," which is a narrower claim than "where diagnostics should run" — gateway, Zephyr guest, or some combination of both is a live, unsettled question elsewhere, and nothing here settles it.
One file changed, in one example, chosen because its length change touches a lot of surface area. It's not a claim about every possible config edit in every example — just this one, run for real, twice, and diffed.
Try it yourself
git clone https://github.com/Xaloqi/EDS cd EDS bash scripts/demo_gtm18_one_did_change.sh
It works on a scratch copy — your checkout's committed example is never touched — and prints the file list that changed plus a reminder of what to diff if you want to see the content for yourself.
One more thing this buys, almost for free: the same regeneration, run twice with SOURCE_DATE_EPOCH pinned to the same value, now produces byte-identical output across the C/header/SOVD-CDA path and the pytest/CAPL path. That second half didn't used to hold — a separate timestamp helper in the commercial test generator had never picked up the fix the first helper got. Small, but it's the same shape of answer as the rest of this post: the thing that makes reproducibility possible is a result of there being one definition everything else derives from, not a separate commitment you have to remember to keep.