Skip to content

v0.28.0

Choose a tag to compare

@github-actions github-actions released this 23 Oct 14:37
· 1264 commits to main since this release

[0.28.0] - 2026-10-23

Headline: Change one thing without restating the rest: alter navigation, alter menu and alter consumed rest service edit a document in place, ALTER PAGE reaches every widget, and create or modify of a flow splices note, caption and error-handler changes instead of refusing them. Alongside it: Protected (add-on) modules and export level are understood by check and MDL, mxcli marketplace package exports a module as a .mpk, rewriting a page no longer deletes the widgets MDL cannot author, and the deprecated v1 BSON driver is gone, leaving mxcli on mongo-driver/v2 alone.

Added

  • alter consumed rest service — add, change and remove operations without restating the service (#1608) — alter consumed rest service M.Api add operation GetBooking ( … ), modify operation PostBooking (Timeout: 60), drop operation Old, set (BaseUrl: '…'). The operation body is CREATE's; modify writes only the keys it lists; actions apply in order, so drop operation X, add operation X (…) replaces X. It edits the stored document in place rather than rebuilding it, so an operation no action names is written back byte for byte — including what Studio Pro stored that MDL cannot say (an optional query parameter, a test value), which a create or modify restating the service resets. Before this the statement did not parse (no viable alternative at input 'alterconsumedrest') and the only MDL route was a create or modify that deleted any operation it did not list.
  • Lint rule CONV020: an object created in a microflow and never committed, returned or passed on (#1518) — $o = create Shop.Item (…) without commit makes an object in memory only; if the microflow then never commits it or hands it to anything that could, the object is discarded when the microflow ends, and check, mx check and the build all pass. In the field an after-startup seed microflow logged "Seeded 20" and the table had 0 rows. CONV020 (warning) reports a create with commit No of a persistent entity in a microflow when nothing in the flow uses the variable except to read a member ($o/Cnt) or change it without commit. Any other mention — commit, change with commit, return, an argument to a microflow, Java/JavaScript action, workflow, mapping or page, a list, an association value, delete, rollback, or the bare variable in any expression — counts as passing it on. It is conservative by design: flow-insensitive (a commit on one branch or in an error handler counts), and nanoflows, non-persistent entities and System/Marketplace modules are not reported; anything it cannot classify keeps it quiet. Docs: "Built-in Rules" › CONV020 details.
  • QUAL004's entry-point prefixes and CUSTOM002's valid prefixes are lint-config.yaml options (#1509) — both lists were constants in the rule files, and mxcli init rewrites those files, so a project whose convention names microflows TEST_, GET_ or PRS_ could not keep an edit and got one finding per such microflow (84 QUAL004 and 121 CUSTOM002 findings on the reporting project). rules.QUAL004.options.entry_point_prefixes and rules.CUSTOM002.options.valid_prefixes take a YAML list or comma-separated text and replace the built-in list; unset, the built-in list applies as before. Underneath, a YAML list or map under any rule's options reached a Starlark rule as None — get_option converted scalars only — so a list-valued option was dropped without a word; lists and maps now convert recursively, the new get_list_option(key, default) builtin reads a list of strings in either spelling, and an option value a rule cannot use is reported as a warning naming rules.<ID>.options.<key> (kept out of the score) while the rule runs on its default. QUAL004 still counts only callers in the model: a microflow called only from tests/*.test.mdl is reported, and its prefix belongs in entry_point_prefixes. Docs: "Starlark Rules" › QUAL004 and CUSTOM002 options.
  • layoutheader and native layout parts — every Atlas layout copies whole — describe layout → create layout of Atlas's Phone_ and Tablet_ layouts dropped their header bar (Forms$Header, printed as NOT re-executable), so pages bound to its HeaderLeft / HeaderRight placeholders failed CE1613 and a copy of Phone_Sidebar or Tablet_Sidebar failed CE0611 "A sidebar toggle button is required if a region can toggle". layoutheader header1 { region left { … } region right { … } } writes it. A native layout's copy silently lost its header placeholder, bottom bar and sidebar — pages bound to Header or a sidebar placeholder then failed CE1613; they are region header { placeholder Header }, showbottombar: true and region sidebar { … }, and the native content is written with the eight keys Studio Pro stores. Measured on a new 11.13.0 app: all 22 Atlas_Core layouts copied, with pages bound to the copied header and sidebar placeholders, build with 0 errors.
  • sidebartoggle — Atlas's responsive sidebar toggle (Forms$SidebarToggleButton) in MDL (#1220) — copying Atlas_Core.Atlas_Default with describe layout → create layout dropped the button that opens the sidebar on phones and tablets: describe printed it as a NOT re-executable comment, and exec gave no warning. sidebartoggle sidebarToggle3 (Tooltip: 'Toggle Menu', ButtonStyle: Primary, Icon: 'Atlas_Core.Atlas_Filled.navigation-menu', Class: 'toggle-btn') takes an action button's caption, tooltip, style and icon, and RenderMode: Link; it has no action. The written widget has the keys of Atlas's own, including its caption under CaptionTemplate rather than the Caption key the vendored metamodel bound. A scroll container region's ToggleMode (ToggleMode: 'ShrinkContentInitiallyClosed'), which the toggle opens, is described and written too — without it the copy failed CE0628.
  • mxcli marketplace package — export a module as a .mpk (#1382) — mxcli marketplace package MyModule -p app.mpr [-o dir] [--all-libs] [--exclude REGEX] [--exclude-managed-dependencies] [--json] is the reverse of marketplace install --file: it wraps Mendix's own mx create-module-package (resolved for the project's Mendix version), so the package is what Studio Pro's Export module package makes, and it installs back with install --file. By default only the module's own required userlib/ JARs go in (mx -l; measured 12.5 KB against 24 MB with Business Events installed); --all-libs bundles every JAR. -l filters only JARs — mx still exports the app's whole resources/ folder (measured: another module's resources/EventLog/ came along) — so after the export mxcli opens the package, warns on stderr about each resources/<X>/, javasource/<x>/ or themesource/<x>/ file of another module, and prints the --exclude pattern that drops them in the one form mx accepts (^resources.EventLog.; a character class or backslash is rejected by mx with exit 1 and a usage message, and an exit 0 that wrote no package is reported as an error too). A module name that is not in the project lists the project's modules instead of failing with mx's exit 3, and mx's exit codes 1–4 are reported with what they mean and what mx printed. mx create-module-package writes into the project it is given — measured on a blank 11.12.5 app: theme-cache/web/theme.compiled.css and its map rewritten, deployment/sass/main.scss added, the module's themesource/ folder created, model files untouched — so it runs on a temporary copy, as docker check does; a before/after listing of the project (sizes and modification times) was identical. The mx that runs must be the project's Mendix version: ResolveMxForVersion prefers an mx on PATH and falls back to any cached one, so mxcli reads the version of the mx it found (its mx.deps.json, else its version-named folder) and refuses a different major.minor.patch, or one it cannot identify, naming both versions and the fix (mxcli setup mxbuild --version X); --mxbuild-path is the override, with a warning on stderr. mx's own output on a successful export goes to stderr, cancelling the command stops mx, and an output directory inside the project is not copied into the temporary copy. Round trip on 11.12.5: export, install --file into a second fresh app, mx check — 0 errors.
  • mxcli search --exclude-marketplace leaves out hits inside Marketplace modules (#1477) — an audit of the app's own text (a leftover TODO, a word that must not ship) reported a vendor module's text beside the project's own. The flag drops hits whose module was downloaded from the Marketplace, including the module's own documentation and domain model documentation; it is off by default, because search is read-only and the default must not change what existing callers see (layout skips Marketplace modules because it writes). A module counts as a Marketplace module by the current snapshot of the catalog only, so one that was a Marketplace module in an older snapshot is not left out of every later search. The SEARCH statement is unchanged. Rules: docs-site "Full-Text Search".
  • alter navigation and alter menu — add, move, replace, drop and set menu items in place (#1522) — adding one menu item used to mean restating the whole menu. alter navigation Responsive { insert after 'Admin' > 'Users' { … }; set (Caption: 'People') on 'Admin' > 'Users'; drop 'Old'; } splices the operations into the stored document and writes it once. An item is addressed by its caption path, outer to inner, with @n for the n-th match; a miss lists what is at that level and an ambiguity lists the shortest unique address of each candidate. check -p --references runs the same resolver without saving. An omitted clause keeps the stored value.
  • ALTER PAGE reaches every widget — a legacy table cell by position (insert into table1 cell(1,2) { … }, replace table1 cell(1,1) with { … }, drop table1 cell(1,2) empties it) and the widgets inside cells; a pluggable widget's object-list items and their slots (insert into acc1 item@2 { … }, acc1 item@1.headercontent) and named slots (dataGrid1.controlbar). set writes a Gallery's or Data grid 2's DataSource, a pluggable widget's attribute property and top-level expression properties, a container's Action: and RenderMode, and a Boolean as true/false. A name that misses lists the item addresses it could have meant.
  • export level and Protected (add-on) modules (#1498) — check -p --references reports what mxbuild refuses as CE1406 (a Hidden element of a Protected module referenced from another module or navigation) and CE5400 (an export level api microflow exposing a Hidden entity or enumeration). Entities, attributes, associations and enumerations take export level api|hidden so such an element can be made usable, a nanoflow's export level api is written, and describe module / describe entity / show modules say Protected and the level.
  • Boundary events on call workflow (#1532) — the clause call microflow already takes. describe printed it for such an activity, and the output did not parse.
  • @anchor names a CASE outcome's sides by its WHEN value — @anchor(from: bottom, Open: (from: right), (empty): (from: bottom, to: top)) case $Order/Status …. Each outcome's side was fixed by WHEN order, and the forms that looked like they set it were refused or ignored.
  • Configuration JVM options reach run --local, and are settable — alter settings configuration and create configuration accept MaxJavaHeapSize and ExtraJvmParameters, describe emits them, and run --local / test --local pass both to the runtime JVM, where they were silently absent (#131). alter settings webui sets OptimizedClient.
  • mxcli run --local --listen-addr — reach a local app from another machine on the network, such as a phone pointed at a dev box; it bound to 127.0.0.1 with no way to widen it.
  • run --ensure-db --db-admin-user / --db-admin-password — a password-protected PostgreSQL superuser, as on a standard Windows install, where every passwordless path failed with "no local PostgreSQL superuser available" (#1485). The password goes to psql through PGPASSWORD, never on the command line.
  • Documentation on mappings, published REST services and data transformers (#1510) — the /** … */ comment above them is stored, so QUAL002's findings can be fixed. Every create or modify used to write an empty documentation over Studio Pro's text.
  • Lint and check — MDL-FLOW05 refuses a statement that can never run, after a return, a raise error, an if/else ending on both sides or a while true with no break (#1491); a snippet call carrying Class, Style or design properties, which never reach the page, is warned (#1511); lint rules can read a user role's security flags (check_security, manage_all_roles, …).
  • mxcli new --container-runtime docker|podman, as mxcli init already had.

Changed

  • check -p applies exec's version gates (#1434) — a statement the project's Mendix version cannot store (create view entity on 9.24, …) passed check and was refused by exec after the statements before it ran. The gates come from one statement-to-feature table that check, exec's preflight and the handlers share.
  • run --local boots with scheduled events running, and says so (#1528) — the runtime's default left every scheduled event silently off. --runtime-setting ScheduledEventExecution=… still wins.
  • marketplace install runs the widget and design-property repairs itself (#1527), where it printed them as a next step and left CE0463 / CE6087 behind.
  • Faster listings on MPR v2 — unit types are indexed by content hash in .mxcli/unit-types.idx, so describe of one microflow no longer opens every unit file in the project, and create member assignments no longer decode every domain model.

Fixed

  • A find, filter or create by an association inherited from another module keeps that association when written short (ako#1637) — over HR.Emp extends Other.Person, describe prints find $l by Other.Person_Dept = $d as find $l by Person_Dept = $d, and the builder looked a bare member up only in HR, so executing that description as a new microflow — or writing the name short by hand — stored the attribute HR.Emp.Person_Dept: check -p and exec passed, then mxbuild failed with CE1613 (11.12.5). Re-running over the stored flow said "Unchanged", so only a rebuild showed it. A bare member name now resolves through the generalization chain to an association of an ancestor in another module, after the inherited attributes.
  • find / filter by System.owner or System.changedBy builds, and describe prints System associations back so they re-execute (ako#1629) — $m = find $l by System.owner = $User; was refused at exec with advice to create a System association, though mxbuild builds it (11.12.5). It now resolves to the System association; on an entity whose generalization root does not store the member, check -p and exec refuse it with the CE0091 "No member selected." mxbuild would report, naming the alter entity that fixes it. describe printed a stored one as by owner, and a System association inherited by a specialization of System.User as by User_Language — which re-executed as an attribute of the specialization, CE1613. A System association in a find or filter is now printed qualified (by System.User_Language), and running the description again changes nothing.
  • marketplace diff and update no longer fail with "path is too long" under the default Windows %TEMP% (#1486) — mx refuses to write any file whose path is 260 characters or longer, on every platform (measured on Linux with mx 11.12.1: a 259-character path is written, a 260-character one refused with the reported "The specified file name or path is too long"), and the reference project was built in %TEMP%\mxdiff<digits>\target-ref. The deepest files mx writes there are bundled node_modules trees — 181 characters in the 11.12.1 blank app, about 202 in NanoflowCommons 7.x — so that layout fitted 7.x for no user name at all. References are now built under the shortest directory the user owns (%USERPROFILE%\.mxcli\t on Windows, /tmp on Linux and macOS, or TMP/TMPDIR when shorter), named in one letter beneath it; never a drive-root folder such as C:\mxt, whose children every local account may modify. A package deep enough to hit the limit anyway gets an error naming the directory, its length, what it leaves for the package, and how to move it. marketplace install builds its reference the same way.
  • marketplace update and install no longer write the package's metadata into the app root (#1487) — after updating Nanoflow Commons 6.2.1 → 7.4.0, manifest.json, License.txt and SiemensMendixNanoflowCommons__7.4.0__READMEOSS_<timestamp>.html sat beside the .mpr as untracked files, because every archive entry but project.mpr and package.xml was copied into the project. Only the files the package's package.xml lists under <files> are installed now — exactly what mx module-import installs, measured on 11.12.1 with ExternalDatabaseConnector 6.3.0 and BusinessEvents 3.12.1, which includes passing over a listed file the archive lacks. What is left out (License.txt, manifest.json, the READMEOSS report, Mendix EULA.docx) is named on one line of the output.
  • create / change naming System.owner or System.changedBy is refused with advice that applies (#1612) — $S = create M.Entity (Name = 'x', System.owner = $User); passed check -p, and exec then refused it with member "System.owner" on entity M.Entity is not a known association … create the association first (create or modify association System.owner from ... to ...), which no project can do. The member cannot be written at all: measured on 11.12.5, System.owner / System.changedBy as a create or change object member is CE1050 "Unknown association 'System.owner'." for every entity — one storing its owner, one not, a System.FileDocument specialization — in a microflow and a nanoflow. check (no project needed) and exec now refuse it as MDL-CHANGE01 and name what works: an association to System.User the project models. find $l by System.owner = $u is not affected; it builds.
  • [id = $obj/System.owner] on an entity that stores no owner is refused before it reaches the build (ako#1306, follow-up on #1358) — the rewrite MDL-RETRIEVE02 suggests passed check -p and exec and then failed mx check with [CE0161] "Error(s) in XPath constraint." when $obj's entity does not store its owner. check -p and exec now refuse a constraint reading System.owner / System.changedBy off a variable whose entity does not store it, judged on the root of its generalization chain (measured on 11.12.5), and name the alter entity <root> add attribute owner: AutoOwner that fixes it; MDL-RETRIEVE02's suggestion states the precondition. The existing check for [System.owner = …] on the retrieved entity now also judges an entity the same script creates, which it skipped — the ordinary shape.
  • Rewriting a page, snippet or layout no longer deletes the widgets MDL cannot author (#1622) — describe page prints a classic data grid, a reference selector, a drop-down or a reference set selector as a -- NOT re-executable comment, so executing that output rebuilt the page without them and said only Replaced page M.P, with mx check clean on both sides. create or modify / create or replace of a stored page, snippet or layout that holds such a widget — one the statement does not declare by name — is now refused, listing each widget, with the alter that edits the document while keeping them and the alter … { drop … } that removes them on purpose first. mxcli check -p reports the same before anything is written, following an earlier alter of the document in the same script.
  • A consumed REST service is written with Studio Pro's list markers — Operations, an operation's Headers and Parameters are typed-array marker 3 (only QueryParameters is 2), measured on Studio Pro-authored services (11.14.0) and on mx convert output (11.6.6). mxcli wrote 2, copied from the legacy serializer, so running a Studio Pro-made service's describe output back rewrote it although nothing had changed — visible once that output parsed (#1608).
  • create or modify consumed rest service names the operations it removes (#1608) — the statement declares the service's whole operation list, so one naming only the operations to add deleted the stored ones — often made in Studio Pro, with their headers, timeouts and mappings — and the output said only Modified rest client: MyFirstModule.BookingApi (2 operations). It still applies, as create or modify entity does for attributes, and now prints ⚠ create or modify consumed rest service MyFirstModule.BookingApi drops 1 existing operation(s) not listed in this statement: PostBooking with the alter consumed rest service … add operation that adds one without touching the rest.
  • describe consumed rest service output runs again: Response: json parses (#1608) — an operation created with Response: json as $Result describes as Response: json, because the variable is not stored (a consumed REST operation has none; the call activity names it), and the parser rejected that form with missing VARIABLE at ')', so describe → edit → create or modify was not available for any such service. Response: json | string | file | status now takes as $var as optional, and describe prints string, file and status responses bare too instead of an unparseable as with nothing after it.
  • mxcli init and init --sync-skills keep a bundled lint rule you edited, and do not recreate one you renamed (#1615) — bundled rules were recognised by file name alone, so an edited example_microflow.star (shipped to be customised) was overwritten by every mxcli init and, since v0.27.0, at every session start by the generated SessionStart hook; moving the edit to a new file name did not help, because the stock file was written back beside it and CUSTOM002 then ran twice, once from the stock list. A bundled file is now refreshed only while its content is a version mxcli shipped (cmd/mxcli/lint_rules_shipped.txt, seeded from the rules' whole git history); any other content is kept, and named once per mxcli version ("Kept 1 edited bundled lint rule(s) … delete the file to take it"). A missing bundled rule is not written while another file defines its rule ID. add-tool takes the same path.
  • A specialization of a non-persistent entity reads as non-persistent (#1600) — describe entity Probe.Child printed create or modify persistent entity Probe.Child extends Probe.Base ( when Probe.Base is non-persistent, so a reader took the child for a table and the output stated a combination Mendix does not have. A specialization stores no persistable flag of its own; it takes its root generalization's, and the reader defaulted it to persistent. The flag is now resolved along the generalization chain, across modules and into the System module (whose HttpRequest, HttpResponse and SoapFault read persistent for the same reason), and the fix reaches everything that reads it: describe, the catalog's EntityType, the domain-model diagrams, and alter entities … where persistent, which used to add its attribute to such a child as well. Re-running the describe output leaves the entity unchanged.
  • go mod tidy runs again, and the expression round-trip test runs for the first time — mdl/exprcheck/roundtrip_test.go imported mdl/backend/mpr (the legacy engine, deleted) plus executor.GetHierarchyForMining and executor.DescribeMicroflowToString (both removed). It never broke a build because it sat behind //go:build roundtrip, a tag no Makefile target and no CI job has ever passed — the only file in the tree using it. go mod tidy considers every build tag, so it was the one command that still saw the file, and it failed with "no matching versions for query latest" on the deleted package, which reads as a dependency problem rather than a dead test. The test is ported to mdl/executor/exprcheck_roundtrip_test.go under the integration tag, where setupTestEnv/describeMDL already provide the real-project harness and make test-integration-executor actually runs it; the removed hierarchy helper is replaced by building the container-to-module map from backend.ListModules(). Its assertion is worth keeping: describing every microflow and re-parsing each expression with the robust parser catches grammar gaps — a missing parsePrimary branch, a missing funcTable entry, a missing slot constraint — that the unit tests cannot, since those only feed the parser expressions someone thought to write down. It now also fails rather than passing silently when the fixture project yields no microflows or no expression slots.
  • An activity with a custom error handler is changed in place (#1489) — under mdl 1, create or modify with one call argument changed, or only the handler's contents changed, was refused and advised alter; the advised alter … replace was refused and advised create or modify. The activity replaced by one handling errors the same way is now swapped in place, keeping the stored handler, its error flow and the merge it rejoins at; the handler's body is diffed like a branch; notes on the activity come with it. A change of the handler's kind is still refused, and every remaining refusal names drop-and-create instead of the other mode.
  • A decision's caption change is set in place (#1610) — adding, rewording or removing an @caption on an if was refused as "changes where its paths end or meet". It is now written onto the stored decision, which keeps its $ID and both paths.
  • Notes on a decision, an end event or the canvas are changed in place (#1517) — a note added to or reworded before an if or the final return, and any change to a free note, was refused. A decision's and an end event's notes are now replaced on the stored node, and the flow's free notes as a whole; nothing else is rebuilt. A note restated before its activity's @position reads as a free note, and the refusal now says so and names the activity it is attached to.
  • check -p predicts a flow statement after an earlier change to the same flow (#1517) — check (and exec's preflight) reported such a statement "not predicted", and exec could then refuse it after the earlier statements had run. Each change check predicts as spliced is applied to an in-memory simulation — never written; any write it cannot hold is refused — so the next statement on that flow is predicted against what exec will see.
  • describe module prints an entity's grants after the associations they name (#1409) — an entity's grant naming one of its associations was printed with the entity, ahead of the association, so replaying the output stopped with "entity ModA.EntA has no member(s) EntA_EntB", leaving the entity without its association or access. check passed the same script; it now reports a grant naming an association created further down the script, with the reorder.
  • import from mapping over an XML schema or web service mapping imports XML (#1232) — the activity was always written with content type JSON, so re-executing a microflow whose import uses a mapping from a web service operation failed mx check with "The selected import mapping cannot handle content type JSON." until the activity was opened in Studio Pro. The content type now follows the mapping's source, and a stored one is kept on rewrite; a REST call's mapping result handling gets the same.
  • Workflow user tasks keep "Auto-assign when targeting results in one user" (#1547) — every single- and multi-user task was written with AutoAssignSingleTargetUser = false, MDL had no spelling for it and describe workflow did not print it, so create or modify workflow turned off a box ticked in Studio Pro. When the targeting then yielded one user, the task was not assigned to them and completing it from a microflow failed at runtime with "You can't complete this user task, it is not assigned to you"; check and mx check were silent. The new clause auto assign single user (on user task and multi user task) writes it, describe prints it when set, and a create or modify or replace activity that does not restate a stored one is refused, like await all users. Omitted, it is off, which is what Studio Pro 11.14 stores for a new task. The MCP backend sent false too and now sends the task's value.
  • check without a project no longer reports a Data grid 2's own settings as dropped, or refuses an expression on one (#1573) — exec -p stores configurationStorageType, configurationAttribute, storeFiltersInPersonalization, refreshInterval, columnsHidable, rowClass, a column's minWidth and the rest of the widget's own keys, and check -p accepts them, but check alone warned MDL-WIDGET07 "not recognized and will be silently dropped on write" for each, and refused an Expression-typed one (rowClass, a column's columnClass) as an MDL-WIDGET42 error, so a script exec takes failed make check-mdl. Data grid 2 has no embedded definition (it is generated from the project's package, and an embedded one would take precedence over that package on every write), so the check judged its keys against the built-in widgets' vocabulary. It now reads the property list of the Data grid 2 template mxcli embeds: a key the template declares is accepted and its declared type decides MDL-WIDGET42. A key it does not declare still warns — a misspelled configurationStorageTyp too, with the spelling it probably meant — but says what is true without a project: that the key cannot be verified, and is dropped on write unless the installed widget declares it. With -p, and for every other widget, nothing changes.
  • describe page prints a Data grid 2's and a Gallery's settings that MDL has no keyword for (#1573) — a pluggable widget takes every setting by its own key (configurationStorageType: localStorage, itemSelectionMethod: rowClick, refreshInterval: 30, a column's minWidth), and exec stores it with mx check clean, but describe page printed only the keyword properties (PageSize, Pagination, Selection, onClick). Executing the description therefore wrote the widget's default over each of them, silently: measured on Mendix 11.12.5, a grid with 22 settings changed came back with the 4 that have keywords, and a Studio Pro-authored grid lost its onClickTrigger: double. describe now prints each setting whose stored value differs from the default, under its key — enumerations and plain values, text templates (with their {N} parameters), expressions, attributes (bare inside a data container, qualified where a bare name would not resolve), a column's own settings and the empty-state placeholder slot. The default is read from the widget's own stored definition, not a list, and a text template counts as default when it holds the text the widget ships for it, so a grid that sets none of them describes exactly as before. A setting with no spelling (an attribute reached over associations) is named in a -- NOT re-executable comment. A Gallery had the same loss and takes the same fix.
  • describe page names the entity an association data source reaches, not the association (#1571) — under a data view, list view, grid or gallery bound over an association, the context comment read -- Context: $currentObject (Shop.Order_Customer): the association, where (Shop.Customer) belongs. The page was right (mx check clean); the comment misled anyone reading describe to learn what a nested widget binds, and the same context decides how an association name inside the container is spelled. The entity is the destination stored on the source's last hop, so it is correct from either end of the association, for a reference set, and for a specialization of the association's end. The DataSource: text is unchanged. A source with no stored destination prints what it did before.
  • check -p and exec refuse a navigation profile or menu document that names a page or flow that does not exist (#1569) — create or modify navigation with a home page, role home, not-found page or menu item (at any depth, in both the deprecated and the mdl 1 spelling) pointing at a missing page, microflow or nanoflow passed check -p ("Check passed!") and exec ("Navigation profile 'Responsive' updated."), and only mx check objected: CE1613 "The selected page 'Shop.NoSuchHome' no longer exists.", once for the home page and once per menu item. A page's own show page and call microflow and an icon name were already resolved; a navigation statement was not, and it rewrites the whole profile. check -p now reports each unresolved target with the profile or menu document, the menu item path (menu item 'Admin' > 'Users') and the missing name, and exec refuses the statement before it writes anything. A target the script creates resolves, wherever it stands in a run that rolls back (plain exec, check), as a call to a flow created further down does (#1405); exec --continue-on-error, the REPL and -c refuse one created further down. A standalone create menu document is covered, and one stored as excluded only warns, as an excluded page does. A login page is not resolved: the sign-in page may be the theme's static login.html rather than a page document.
  • A bare returns Module.Name that names an enumeration is accepted by exec, as check already said (#1562) — create microflow M.F () returns M.Variant as $V (and the nanoflow and rule forms) passed check -p --references and was then refused by exec with "entity 'M.Variant' not found for return type". The parser cannot tell an entity from an enumeration for a bare qualified name; exec resolved that for parameters (the entity first, then an enumeration) but not for return types. All three kinds now do, and store exactly what returns enum M.Variant stores. A rule's check refused the bare form too, with a misleading "a rule must return a Boolean or an enumeration, not Entity". A name that is both an entity and an enumeration stays the entity, as for a parameter, and a name that is neither now says "entity or enumeration" in check and in exec, for return types and parameters alike, where it used to say only "entity". returns enum M.Variant and Enumeration(M.Variant) always worked and are what describe prints.
  • A JavaScript action's parameters are written with the names mxbuild generates (#1560) — create javascript action … (Url: String, ReadOnly: Boolean) wrote export async function JS_Open(Url, ReadOnly), while mxbuild and Studio Pro regenerate the signature with the first character of each parameter lower-cased, JS_Open(url, readOnly), and keep the body. A body using the declared Url then threw ReferenceError the first time the action ran, with check, mx check and the build all clean. The signature and the JSDoc @param names now carry that name (only the first character changes: URL is uRL, MyID is myID, _Lead stays), measured against mxbuild 11.12.5, so what exec writes is what a build leaves. The same rewrite used to break a Studio Pro-authored action on its first edit through MDL: Geocode(address, …) became Geocode(Address, …) over a body that still read address. The model keeps the declared parameter names, describe prints them as declared, and bodies are not rewritten, so a body reads readOnly, not ReadOnly.
  • A quoted Editable: '<expression>' is stored as a conditional editability instead of being dropped (#1567) — CHECKBOX cb (Attribute: Flag, Editable: '$currentObject/Locked = false') passed check, exec said "Created page", and the widget was stored Editable: Always with no condition, so a field meant to be locked stayed editable (mx check also reported 0 errors). Visible: '<expr>' beside it was stored as a conditional visibility, and the unquoted Editable: $currentObject/Locked = false worked: the page builder read Editable only as one of Always/Never/Conditional and skipped every other string without a word. A string that is not one of those is now the condition, exactly as for Visible, and describe prints it back bare, so a describe → exec round trip is stable. Editable: false / true on an input widget were dropped the same way and now store Never / Always (a list view and a grid column keep their own plain boolean, and a data view keeps its existing handling). check refuses what still cannot be stored — a misspelt Editable: Neever, a quoted text that is not an expression, Editable: Conditional with no condition — as MDL-WIDGET19 naming the accepted spellings, and a quoted condition now gets the checks the bare one gets (MDL-WIDGET13 association steps, MDL-BUTTON02, MDL-WIDGET41 on a pluggable widget that declares no Editability, MDL-WIDGET20, and MDL-WIDGET31 no longer warns about an input marked editable: false). alter page … set (Editable: '<expr>') still refuses a quoted condition with its existing message.
  • mxcli run --local --detach no longer opens a console window per child process on Windows (#1484) — a detached run on Windows made several terminal windows pop up during a boot, one for each console program it started (Java, MxBuild, Gradle). The run was created without a console (DETACHED_PROCESS), so Windows gave each console child a new, visible one. It is now created with a console of its own that has no window (CREATE_NO_WINDOW), which its children share. Measured on Mendix 11.12.5: v0.26.0 showed 4 new visible windows during a boot, this build 0. Closing the launching terminal still does not end the run, and run stop is unchanged.
  • mxcli test tests finds test files in subfolders (#1363) — mxcli test tests --list printed Found 0 test(s) when the suites live in tests/<Module>/*.test.mdl, while mxcli test tests/Sales --list found them: a directory argument only read the files directly inside it. It now reads *.test.mdl and *.test.md at any depth, in path order, skipping hidden folders (.git, .mxcli) and folders starting with _ (the Go convention, so a positive-control suite can sit in tests/unit/_control outside the default run; the folder you name is always read, so mxcli test tests/unit/_control runs it). Skipped _ folders are named after the Found N test(s) line of a run and at the end of a --list, so they do not disappear silently, and --list over a tree with no tests now says why on stderr (it still exits 0; a run with no tests still exits 1). A file that cannot be parsed is still reported on its own and does not stop the others; neither does a subfolder that cannot be read (a locked folder, a root-owned one in a mounted volume), which is reported by name and turns the run red while the tests in the other folders still run. The "no tests found" hint now also names misnamed .mdl / .md files in subfolders and the _ folders that were skipped.
  • change through an association is refused with the working idiom instead of shipping a project mxbuild rejects — change $a (A_B.Title = 'x') (a member whose dotted qualifier is an association, not a module) passed mxcli check, exec reported success, and the next build failed with CE1613 "The selected association 'A_B.Title' no longer exists": Mendix's Change object activity cannot set an attribute through an association at all (probe matrix in the bug report — every encoding, including the ones an older mxcli wrote, is rejected or unloadable), and the writer's one-dot heuristic categorized the name as an association-set verbatim. check -p now reports the form and exec refuses it, both naming the retrieve-then-change replacement (retrieve $obj from $a/A_B; then change $obj (Title = …)); the legal shapes — Module.Assoc association-sets and Module.Entity.Attribute qualified attributes — are untouched. Example: mdl-examples/bug-tests/change-member-through-association.mdl.
  • run --local --watch recovers from two mxbuild --serve faults — on versions where mxbuild writes web/dist itself, every build after the first in a serve process dropped the pages (every page 404 after an "applied via reload"), and after a Sass error the process re-reported it on every later build even once fixed (ako#1485, #1580, #1577). The watch loop now restarts the serve process in both cases.
  • check -p predicts more of what exec and the build refuse:
    • a page following a view entity's association from its target (CE8812), a String-returning widget expression given a non-String literal (CE0117, #1611), a combo box in enumeration mode on a non-enumeration attribute, bound to an association through Attribute (#1440), or in association mode with no object to start from (#1420);
    • a list retrieved over an association used as an object (#1516), an association path as a return value or loop list, an undefined glyph code (MDL078, #1522), the variables a workflow expression names (#1529), a call workflow mapping not keyed WorkflowContext (#1530), built-in argument kinds, also without a project (#1515), and an unknown alter settings section or key;
    • a published OData entity with no KEY or a non-unique single KEY (#1426), and business event message attributes against inherited members (#1525);
    • accepted where it refused: an ALTER ENTITY on an entity an earlier MOVE or RENAME produced, page data source XPath over members the same script adds, an ALTER PAGE column address no earlier operation adds, a bare System enumeration in a flow signature, and a one-hop variable comparand in a widget data source XPath (#1466).
  • describe → exec keeps what it used to change or drop — a view entity's stored OQL (re-indented), its outer comments, and its associations (printed as statements); an XML-schema mapping's root; a gallery's sort over an association (#1599); a combo box's and a data grid filter's settings (#1586); an untitled Data grid 2 actions column (#1520); an Accordion group's header content; a call workflow's parameter mapping (#1531); business event channel identity (#1525); statements after an if, and loop bodies, inside an error handler; the line breaks an author gives an XPath constraint; a Java action parameter's description and return value name (#1588). describe module … with all covers a large module.
  • Pages — a drop-down filter keeps its attribute and hidden column properties are reset after an ALTER SET (#1519); a column SET keeps the dynamic text template it hides or shows (#1617); an item keeps its hidden slots and texts; hidden text templates inside object-list items are null, as Studio Pro stores them (#1198); a control-bar show page can pass the grid's selection; an older Data Widgets definition is rebuilt correctly on 10.x (#1424); a pluggable attribute of another page parameter is written $Param.Attr.
  • Flow layout — a splice making room leaves the flow's parameters where they are, a node the script places is not moved, a note added in a splice does not re-lay out the row, a @curve inside a loop body is kept (#1597), and merges after loops, error-handler rows and note lines land where Studio Pro draws them.
  • Integration — an OpenAPI import refuses Swagger 2.0 instead of importing an empty contract; partnered OData navigation properties import (CE6618); create external entities names entities after the entity type; inherited OData members are published under their declaring entity (CE1613); a one-to-one association in a message definition builds (CE6524); a SOAP mapping's imported web service is read.
  • A partial revoke from several roles narrows each of their rules (#1501), and a note's line endings no longer count as a change to a spliced flow (#1505).
  • The Studio Pro MCP backend — ALTER MICROFLOW insert lands on Studio Pro 11.15, a microflow created over MCP keeps its flow sides and notes, and a foldered document's module is resolved.
  • Lint builtins — parse_xpath keeps association paths, documents() returns message definitions, permissions() has nanoflow execute grants, scheduled_events() names the module of an event in a folder, the catalog records every document a widget names (#1513), and user_roles() exposes a role's description.
  • Tools — mxcli new scaffolds a layout that builds on Mendix 10.24 and fails when it does not (#1433); create configuration on 11.12+ mints fresh ids for the copied OpenTelemetry part; describe settings shows the project id (#1526); git note --write no longer loses notes when its reader exits early; docker check no longer claims widgets were normalised after update-widgets failed; the devcontainer's postCreateCommand works on Podman on Windows; syntax errors hint a keyword used as a name, returns after option clauses (#1533) and a routine header without parentheses (#1514).

Security

  • The deprecated v1 BSON driver is gone: mxcli runs on mongo-driver/v2 alone — go.mongodb.org/mongo-driver v1.17.10 is removed from go.mod, and go version -m bin/mxcli now lists only mongo-driver/v2 v2.9.2. It was the last component left on a customer's blocking dependency scan, flagged for CVE-2026-107325, which has no fix on the v1 line and never will — v1.17.10 is that line's final release and the module is marked deprecated, so no upgrade could ever have cleared it. mxcli was never exposed (the panic is in bsoncore.Array.Validate, which no file imported), but the finding would have recurred on every scan.
  • New modelsdk/bsoncompat — the decode shims that make v2 read like v1 — the two drivers decode nested documents into different Go types, and mxcli's type assertions were written against v1's. Measured on identical bytes: a bson.D destination nests primitive.D under v1 and bson.D under v2 (equivalent); a bson.M destination nests primitive.M under v1 but bson.D under v2; a map[string]any destination nests map[string]any under v1 but bson.D under v2. The last two silently break a doc["Key"].(bson.M) read — a failed type assertion is not an error, so the value is dropped and the next write emits a document with a property missing, which Studio Pro refuses to merge (the canon.CompletePropertySets class, #1373). UnmarshalDocM and UnmarshalDocMap restore v1's shape at every depth, arrays included; DMap replaces the bson.D.Map() method v2 removed. The two Unmarshal helpers are not interchangeable — bson.M is a named type, so a bson.M value does not satisfy .(map[string]any) — and a bson.D destination needs neither, since applying one there re-introduces the bug inverted. All three shapes are pinned by tests that fail loudly if a future driver release makes a helper unnecessary, rather than passing vacuously.
  • A guard keeps v1 from coming back — TestNoV1DriverImports walks the tree for the v1 import path and TestGoModDoesNotRequireV1Driver reads go.mod, because re-adding either is not a build error and nothing else would notice until the next dependency scan. Verified by control: both name the offending file and the offending go.mod line.
  • The klauspost/compress v1.20.3 floor survives go mod tidy — the v1 BSON driver removal ran go mod tidy, which deleted the hand-written github.com/klauspost/compress v1.20.3 requirement, because no package mxcli builds imports it. The module graph fell back to the v1.19.2 that mongo-driver/v2 declares, which is flagged for GHSA-fpmh-frq8-7rj8, so the dependency scan would have blocked the release again. A new internal/depfloor/floor.go imports github.com/klauspost/compress/zstd behind a depfloor build tag that no build sets. go mod tidy considers every tag, so the requirement is now direct and kept, while go version -m bin/mxcli still lists no klauspost/compress. TestGoModKeepsSecurityFloors fails when go.mod lacks a floored module or requires it below its floor, and it also pins mongo-driver/v2 at v2.9.2.
  • Dependency scan follow-up: klauspost/compress and go-mssqldb raised to their fixed versions — github.com/klauspost/compress v1.19.2 -> v1.20.3 and github.com/microsoft/go-mssqldb v1.10.0 -> v1.11.2. A second scan of the published .exe, run after the mongo-driver upgrade, still blocked the release. klauspost/compress v1.19.2 predates GHSA-fpmh-frq8-7rj8 (a zstd DecodeAll crash on crafted input, fixed in v1.20.2) — the earlier upgrade had only raised it to whatever mongo-driver/v2 declared, which is not a security floor; it is now an explicit requirement. As before, that module is compiled into no mxcli build (no go.sum entry, no go version -m line), so the bump moves only the requirement graph the scanner reads. go-mssqldb is a blank import in sql/drivers.go for driver registration and mxcli calls none of its API, so the bump has no API surface; TestExternalDriversRegistered now pins that contract, which had no coverage, and fails with driver "sqlserver" is not registered when the import is dropped.
  • go.mongodb.org/mongo-driver v1 remains flagged and cannot be upgraded out — CVE-2026-107325 has no fix on the v1 line (v1.17.10 is its last release and the module is deprecated). mxcli is not exposed: the panic is in bsoncore.Array.Validate, which mxcli imports in no file, every .Validate() call site in the tree resolves to the v2 driver, and v1's bson.Unmarshal — the API mxcli does use, on .mpr bytes — does not panic on the malformed payloads tried. Measured on v1.17.10: bsoncore.Array.Validate([]byte{0,0,0,0}) panics with index out of range [-1], while bson.Raw.Validate on CVE-2026-107324's math.MaxInt32 payload does not, so that second CVE's code path is v2-only and the v1 report overstates it by one. Clearing the finding at source needs the remaining v1 bson imports moved to v2, which is tracked separately.
  • Go 1.27.3 for GO-2026-6809, reported by govulncheck.
  • npm advisories cleared in the vega-charts widget and the VS Code extension — in-range audit fixes, and sprintf-js dropped from the vega-charts widget.

New Contributors

Full Changelog: v0.27.0...v0.28.0