Schema Versioning
Profile JSON schema versioning policy: additive changes, breaking changes, MIN_SCHEMA_VERSION / MAX_SCHEMA_VERSION, and the codegen compatibility matrix.
Profile Schema Versioning
Each profile file (mig.json, ahb.json, codelists.json) carries a top-level
schema_version integer that describes the structure of that file. The codegen
in xtask/src/codegen.rs enforces a range — it rejects profiles whose version
is below MIN_SCHEMA_VERSION (format too old) or above MAX_SCHEMA_VERSION (format
too new for this tool version).
MIN_SCHEMA_VERSION ≤ schema_version ≤ MAX_SCHEMA_VERSION
Both constants live in xtask/src/codegen.rs inside discover_profiles().
Versioning policy
Additive-only change (new optional field) — do NOT bump version
New optional fields are added to the Rust struct with #[serde(default)] so
existing JSON files without the field continue to parse cleanly under the
deny_unknown_fields policy.
Procedure:
- Add the new field to the struct with a
#[serde(default)]annotation. - Add the field to the JSON Schema with
"default": …(seeprofiles/schemas/). - Optionally populate the field in affected JSON files.
- No version bump required;
--checkremains green without touching every profile.
Example: The archived field (v1 addition).
Structural / breaking change — bump MAX_SCHEMA_VERSION
A breaking change removes or renames an existing field, changes its type, or alters its semantics in a way that requires all profiles to be updated.
Procedure:
- Bump
MAX_SCHEMA_VERSIONinxtask/src/codegen.rs. - Add conditional deserialization in
discover_profiles()to handle both the old and new version (or drop support for the old version by raisingMIN_SCHEMA_VERSION). - Update every profile JSON file to the new
schema_version. - Run
cargo xtask codegenand commit the regenerated output.
Error messages
| Situation | Message |
|---|---|
schema_version < MIN_SCHEMA_VERSION | "has schema_version N (minimum is M) — update the profile JSON file to at least schema_version M" |
schema_version > MAX_SCHEMA_VERSION | "has schema_version N (maximum supported is M) — this profile was authored for a newer codegen; update xtask/src/codegen.rs to support schema version N" |
The archived field (profile lifecycle)
Profiles have an optional valid_until date (ISO 8601, e.g. "2026-09-30").
Once the BDEW transition grace period has elapsed, the profile should no longer
be compiled by default.
A profile moves through three compilation states over its lifetime:
stateDiagram-v2
[*] --> Active : profile registered
Active --> Expired : valid_until date passes
Expired --> Archived : cargo xtask codegen --prune-expired<br/>(valid_until + grace_days elapsed, default 90 days)
- Active — compiled under the plain feature (e.g.
mscons). - Expired — past
valid_untilbut still compiled;archivedis not yet set. - Archived —
archived: trueinmig.json; now gated behind the{type}-archive/archivefeature instead of the plain feature.
The archived boolean field in mig.json is the explicit, deterministic marker
for the final transition. It is set by cargo xtask codegen --prune-expired:
cargo xtask codegen --prune-expired [--grace-days N]
Default grace period: 90 days after valid_until.
What happens when a profile is archived
mod.rsgates it behind#[cfg(any(feature = "{type}-archive", feature = "archive"))]instead of the plain#[cfg(feature = "{type}")].- Users who only enable
msconswill no longer compile expired MSCONS profiles. - Users who need historical validation enable
mscons-archive(orarchivefor all types).
Why use an explicit JSON flag (not a date comparison at codegen time)?
If mod.rs were generated by comparing valid_until against now(), the output
would differ every time the clock passed another expiry. This would cause
cargo xtask codegen --check (the CI drift guard) to fail on an unrelated PR
just because calendar time advanced.
The explicit "archived": true JSON field makes the generated mod.rs fully
deterministic — identical output on every run until someone explicitly calls
--prune-expired and commits the updated JSON and regenerated mod.rs.
Annual workflow
# 1. Mark newly expired profiles as archived
cargo xtask codegen --prune-expired
# 2. Review which profiles were archived
git diff profiles/
# 3. Regenerate (already done by --prune-expired, but re-run for --check confirmation)
cargo xtask codegen
# 4. Verify everything still compiles
cargo check --all-features --all-targets
# 5. Commit both the mig.json updates and the regenerated mod.rs
git add crates/edi-energy/profiles/*/mig.json crates/edi-energy/src/generated/mod.rs
git commit -m "chore: archive expired profiles after BDEW format update"Cargo features
| Feature | Effect |
|---|---|
utilmd | Current UTILMD profiles only |
utilmd-archive | Current + archived UTILMD profiles |
archive | Current + archived profiles for all message types |
See crates/edi-energy/Cargo.toml for the full feature list.
Schema version
Every generated profile JSON carries a schema_version so the codegen can reject a
profile written for an incompatible shape. The current schema is version 1, which
defines the base fields release, valid_from, valid_until, archived, segments,
pruefidentifikatoren, conditional_rules, segment_rules, and group_rules.
The accepted range is bounded by MIN_SCHEMA_VERSION and MAX_SCHEMA_VERSION
(both 1) in discover_profiles() (xtask/src/codegen.rs); a profile outside that
range fails codegen rather than being silently misread.