Idempotent Writes
Re-running an MDL script against a project that is already in sync leaves the
.mpr and mprcontents/ files byte-identical. git status stays clean and
Studio Pro shows no version-control changes, because the write does not happen at
all.
This matters beyond tidiness: without it, git diff cannot answer “did this
script change anything”, two people running the same script commit different
bytes, and a .mxunit merge conflict is not resolvable by hand.
Why a write used to happen anyway
create or replace does not reconcile. It rebuilds the document from the MDL and
overwrites, and every sub-element in that rebuild is a new object with a freshly
random $ID. The stored bytes were a function of the script and a random
source, so no amount of care in the builders could have made them stable.
What mxcli compares
Before writing a unit, mxcli compares the new document against the stored one in
a canonical form: every element $ID is replaced by its index in a
deterministic walk, so a difference in which UUIDs were minted is not a
difference. If the two are canonically equal, the write is skipped and the stored
document — with its existing IDs — stays exactly as it was.
Skipping is safer than writing, not merely cheaper. The stored IDs are the ones every pointer inside that unit already agrees with, and nothing outside the unit can observe them: cross-document references are by qualified name.
The comparison is biased toward writing. If anything cannot be decided, mxcli writes. A redundant write costs you a diff; a wrongly skipped write would lose your edit.
Identity is preserved, not re-minted
A microflow carries a StableId, which Mendix declares as an identifier and
which the build turns into the operation id the browser uses to call that
microflow. mxcli carries the stored value onto the rebuilt document instead of
minting a new one, so re-running a script does not renumber operations in your
deployed model.
Both engines, every write path
The policy lives in one place (modelsdk/canon) and is applied at the single
write choke point of both the default modelsdk engine and the legacy
engine. Which engine ran is an --engine flag, and it must not be visible in
your diff.
Turning it off
MXCLI_ALWAYS_WRITE=1 mxcli exec script.mdl -p app.mpr
Every write lands, whether or not anything changed. This exists for bisecting a
suspected elision bug (“does it still reproduce if nothing is skipped?”) and is
not a supported option. It disables skipping only — identity is still
preserved, because a forced write that re-minted StableId would change the
deployed app rather than help you debug it.
Verifying it yourself
Run your script twice against a settled project and compare the stored units:
find mprcontents -name '*.mxunit' | sort | xargs sha256sum > before.txt
mxcli exec script.mdl -p app.mpr
find mprcontents -name '*.mxunit' | sort | xargs sha256sum > after.txt
diff before.txt after.txt # expect no output
Two cautions, both of which produce a meaningless zero:
- Make sure the script is actually re-runnable. A script containing
create moduleorcreate enumerationfails on the second run and writes nothing, so the diff is empty for the wrong reason. Check the run’s output. - Run the control. Repeat with
MXCLI_ALWAYS_WRITE=1and confirm the diff is non-empty. If it is empty too, your measurement cannot detect churn and the clean result proves nothing.
For a per-unit view of what would be skipped, scripts/mprsnapshot -canon emits
canonical digests keyed by unit id.
See ADR-0008 for the decision and the measurements behind it.