Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .claude/skills/fix-issue/findings/mdl-executor.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -868,5 +868,7 @@
{"area": "mdl/executor", "date": "2026-10-05", "symptom": "On Mendix 11.15, `TestMxCheck_DoctypeScripts/40-message-definition-examples.mdl` fails with CE0270 \"No root element could be found in the schema\" at MsgTest.IMM_Order. On a converted or Studio Pro 11.15 project, `list message definition collections` finds nothing, and `describe import mapping` prints no source", "cause": "Mendix 11.15 replaced `MessageDefinitions$MessageDefinitionCollection` with one `MessageDefinitions$MessageDefinition2` document per definition. `mx convert` turns the collection into a `Projects$Folder` with the same unit ID and name, and the ExposedEntity tree is byte-identical apart from $IDs. mxcli read and wrote only collections, so on 11.15 there was nothing to resolve a mapping against", "file": "`mdl/backend/modelsdk/messagedefinition_document.go` (read/write, reusing the collection's exposedNodeFromGen/messageNodeToGen), `mdl/executor/cmd_messagedefinition_documents.go` (create/describe/drop/list + version refusals), `mdl/executor/mapping_messagedefinition.go` (`findMessageDefinition(ctx, ref)` returns the stored reference), `cmd_messagedefinitions.go` (alter target resolution, association-drop guard), grammar `createMessageDefinitionStatement` / `DROP|DESCRIBE MESSAGE DEFINITION` / `LIST MESSAGE DEFINITIONS`", "insight": "**`mx convert` is the oracle for a storage change.** Converting a known 11.14 project gives a Studio Pro-authored 11.15 reference for the new shape and for how references move, so no guessing is needed. **Under the revert, 11.15 fails earlier than CE0270.** A two-part name written into the old `MessageDefinition` key makes mxbuild refuse to load the project (`StorageLoadException: ... is not a valid OldMessageDefinitionIdentifier`). The old key is typed as a three-part identifier even though 11.15 no longer reads it. The doctype script uses `-- @version: ..11.14` / `11.15+` sections, and the 11.15 mapping needs a different name from the older section's, because a plain `check` sees both sections and reports MDL-DUPDEF", "refs": ["ako/mxcli#987"]}
{"area": "mdl/executor", "date": "2026-10-06", "symptom": "v0.25.0: `describe microflow` 4-8x slower than v0.24.0 on a flow arranged by hand in Studio Pro, and the cost grows faster than the flow (60 if/else blocks: 4.7 s -> 35.6 s); flows laid out by mxcli barely change", "cause": "The canonical describe's layout derivation (#748) re-renders the description every round, and a hand-laid flow takes gatedLayoutRounds+2 = 8 rounds. Each render re-ran the stored flow's split/merge analysis (findSplitMergePoints, labelCrossedMerges) and the body warnings (postDominators via microflowgraph.Analyze) - superlinear, and independent of which annotations the round keeps", "file": "`mdl/executor/cmd_microflows_derived_layout.go` (flowAnalysisMemo, installed by useDerivedFlowLayout, rebuildOnly view for the rounds), `mdl/executor/cmd_microflows_show.go` (findSplitMergePoints / labels / microflowBodyWarnings go through it)", "insight": "**Profile before believing the issue's theory.** The report blamed the round count, but rounds were already capped at 8; the rebuild itself was cheap. The CPU profile showed the cost was the *render* inside each round - the stored flow's graph analysis, redone per round. Memoising it per describe (keyed on the stored collection pointer) and leaving the `-- WARNING` comment lines out of rounds (the rebuild never reads them) gave 14.2 s -> 1.7 s at 60 blocks, with the printed description byte-identical to the unfixed build. A wall-clock assertion would be flaky; the test counts split/merge analyses per describe (18 over 8 rounds before, 2 after), with the round count as its control", "refs": ["mendixlabs/mxcli#1301"]}
{"area":"mdl-executor","date":"2026-10-05","symptom":"A view entity selecting a **non-localized** DateTime column (Studio Pro's \"Localize\" unticked — the normal choice for a calendar date) passes `mxcli check --references` and exec, then `mx check` fails CE6770 \"View Entity is out of sync with the OQL Query.\" The view attribute was always written LocalizeDate = true, and MDL has no spelling for the flag, so describe -> exec re-broke it on every run","cause":"execCreateViewEntity built every view attribute with convertDataType, whose DateTime is Mendix's default LocalizeDate = true; nothing looked at the attribute the column reads","file":"`mdl/executor/oql_view_localize_date.go` (viewDateTimeLocalize), `mdl/executor/cmd_entities.go` (execCreateViewEntity); test `mdl/executor/view_entity_localize_date_test.go`; bug-test `mdl-examples/bug-tests/1297-view-entity-non-localized-datetime.mdl`","insight":"**Derive, don't add syntax**: on a view entity the column's localization is a property of the query, so the fix reads it from the source attribute and describe -> exec round-trips (`Unchanged`) with nothing new to spell — same reasoning as the view association (the column is the declaration). **Measure the shapes before choosing the scope**: one project, one view per shape, mxbuild 11.12.5 — pass-through, `s/Attr`, MIN, MAX and CASE over a non-localized source are ALL CE6770 when written localized and 0 errors when patched to false, so a pass-through-only rule (the obvious one, mirroring the string-length rule) would have fixed the report and left MAX(date) broken. Sources that disagree (coalesce of a localized and a non-localized column) are unmeasured and keep the default. **Repro needs a Studio Pro-authored flag**: mxcli writes every persistent DateTime localized, so the bug is invisible from MDL alone — the reporter's pymongo patch flipping Sale.SaleDate is the cheapest stand-in. Control: stubbing the assignment fails 5 of 6 cases with `LocalizeDate = true`; pre-fix binary on the same project gives 5 × CE6770, fixed gives 0","refs":["mendixlabs/mxcli#1297"]}
{"area": "mdl/executor", "date": "2026-10-07", "symptom": "`set $Cursor = $Next;` where both are OBJECT variables (Reference retrieves followed from the FROM entity) passes `check --references` and `exec` (\"Created microflow\"), is written as a Microflows$ChangeVariableAction, and mx check reports [CE7247] \"Variable 'Cursor' does not have a primitive type.\" Natural to write when walking a parent chain in a `while` loop", "cause": "`set` on a plain variable had two outcomes: Change list Replace for a known list (ako/mxcli#949) and Change variable for everything else, so an object fell into the primitive-only action. Mendix has NO action that reassigns an object variable. Check could not have caught it even with a guard: the check-time validator (validateFlowBody) typed every association retrieve as `List of <assoc>`, because it had no association multiplicity, so the forward-Reference object read as a list", "file": "`mdl/executor/cmd_microflows_builder_actions.go` (`objectVariableType`, `refuseSetOnObject`), called from `cmd_microflows_builder_graph.go` (exec) and `cmd_microflows_builder_validate.go` (check); `validate.go` passes `checkAssociationShapes(ctx, sc)` into `validateFlowBody` so `forwardReferenceTarget` types a forward Reference retrieve as an object", "insight": "A refusal is only as good as the variable typing under it, and check and exec type variables differently: exec asks the backend for the association, check guessed `List of`. A guard added to the check validator alone passes the reported repro because the object it should refuse is, to check, a list. Write the check-path test against the REPORTED shape (association retrieve), not an object parameter, or it goes green while the issue stays open. Reuse `checkAssociationShapes` (project + script associations, built for MDL-ASSOCDS01) rather than a new lookup. Type only the unambiguous shape (Reference followed from FROM, not a self-association): Mendix types a self-association retrieve as a list, and that `set` is a valid Change list Replace. Measured on 11.14.0: faulted build exec -> CE7247; the self-association and recursive sub-microflow forms -> 0 errors. Plain `check` with no -p does not run validateFlowBody at all, so a `.fail.mdl` cannot pin this", "refs": ["mendixlabs/mxcli#1323", "ako/mxcli#949"], "ce": ["CE7247"]}
{"area": "mdl/executor", "date": "2026-10-07", "symptom": "Plain `mxcli check` (no -p) passed `set $X = $Y;` on an OBJECT variable (entity parameter, create, retrieve first, loop iterator, head) even after check --references and exec refused it; the build then failed with CE7247. The bug-test could not be a .fail.mdl because check-mdl runs plain check", "cause": "Plain check runs only the MDL0xx rule set (ValidateProgram -> ValidateMicroflow/ValidateNanoflow); validateFlowBody, where the first #1323 refusal lived, runs only under --references (validate.go) and in exec. A refusal added to one validator is invisible to the other path", "file": "`mdl/executor/validate_microflow_set_object.go` (`checkSetOnObjectVariable`, MDL-SET01), wired from `validate_microflow.go` (validate) and `validate_nanoflow.go`; `cmd_microflows_builder_validate.go` now reports only `assocObjectVars`", "insight": "Know which validator each entry point runs before adding a refusal: plain check = ValidateMicroflow; --references = that PLUS validateFlowBody; exec = the builder (plus execEnforcedMicroflowRules). Putting the rule in both check validators made --references print it twice; split by what each can see — the rule takes every object visible in the text, validateFlowBody only the association-typed ones that need the project. Measure every producer a rule judges, with the faulted build: all five plus a nanoflow gave CE7247 on 11.14.0, but a PARAMETER gives different wording, \"Parameter 'A' cannot be changed.\" — and that wording also fires for a PRIMITIVE parameter (`set $N = 1` on `$N: Integer`), a separate unrefused gap the object-only rule does not cover. Leave `find` out of the object producers: it is also the String function", "refs": ["mendixlabs/mxcli#1323", "ako/mxcli#1011"], "ce": ["CE7247"], "rules": ["MDL-SET01"]}
{"area": "mdl-executor", "date": "2026-10-06", "symptom": "`create constant M.Flag (Type: Boolean, DefaultValue: true)` (or `True`) stores DefaultValue \"true\". Studio Pro stores \"True\"/\"False\"; its constant dialog shows a stored \"true\" as False while the runtime reads it as true, so the developer sees one value and the app runs with another. `mx check` is silent and `describe constant` prints `true` for both, so only the stored unit shows it. Quoted `'True'` was the workaround. `alter settings constant @M.Flag value true` (or 'true') wrote the configuration override the same way", "cause": "createConstant rendered the AST literal with fmt.Sprintf(\"%v\"), so a Go bool became Go's lowercase \"true\"; the quoted string passed through verbatim, which is why only 'True' was right. The settings path stored the visitor's token text verbatim and never looked at the constant's type", "file": "`mdl/executor/cmd_constants.go` (storedConstantDefault, used by the create and create-or-modify branches, and by `alterSettingsConstant` in `mdl/executor/cmd_settings.go` via settingsConstantType); test `mdl/executor/cmd_constant_boolean_default_test.go`; bug-test `mdl-examples/bug-tests/1321-boolean-constant-default-case.mdl`", "insight": "**`%v` on an AST literal is Go's spelling, not Mendix's**: any place that stringifies a parsed value into a stored property must normalise to the platform's form. The case-insensitive reader (formatDefaultValue's EqualFold) hid the writer's defect from describe, so a round-trip test could not catch it; assert on the value handed to the backend. Neither `mx check` nor the runtime distinguishes the two spellings — only Studio Pro's dialog does — so a build cannot verify this; the evidence is the reporter's Studio Pro 11.12.4 measurement plus the stored unit. Control: test written before the fix failed 5 of 6 spellings with `stored as \"true\"`/`\"false\"`, the quoted 'True' control passing; exec on a copy of testdata/pedapp then stores True/True/True/False. **Enumerate every write path for the value, not just the reported one**: the configuration override holds the same typed value and had the same defect; it needs the constant's type looked up (a String constant holding 'true' must stay lowercase). Control: the settings test failed 3 of 5 cases with `stored as \"true\"`/`\"FALSE\"` before the fix, the 'True' and String-constant controls passing; the stored Settings unit on a pedapp copy read 'true' for `value true` and `value 'true'` before, 'True' after, the String override 'true' both times", "refs": ["mendixlabs/mxcli#1321"]}
{"area": "mdl/executor", "date": "2026-10-07", "symptom": "A button added to a snippet with `alter snippet … insert` (or replace) calling `call microflow M.F(P = $P)` with a snippet parameter fails mxbuild with CE0115 \"The arguments that are passed to microflow 'M.F' do not match the expected parameters and need to be refreshed\" on that button only; the same button in CREATE SNIPPET or via ALTER SNIPPET SET is clean", "cause": "`buildWidgetsFromAST` and `buildColumnSpecsFromAST` (mdl/executor/cmd_alter_page.go) seeded paramScope and localVariables from the mutator but not `isSnippet`, so `classifyFlowArgValue` returned kind \"parameter\" and the Forms$PageVariable named PageParameter instead of SnippetParameter", "file": "`mdl/executor/cmd_alter_page.go` (isSnippet from mutator.ContainerType() in both builders)", "insight": "**A pageBuilder built outside CREATE needs THREE scope fields, not two** — paramScope, localVariables and isSnippet; #1317 found the SET builder missing all three and this one missing the last. When adding a builder, copy the seeding from convertASTAction rather than from memory. **Unlike the Expression-form defect (#1140/#1317), mxbuild DOES catch the wrong slot** — CE0115 rather than CE1571 — so one script exercising CREATE, INSERT and SET on the same snippet with `mx check` is a complete A/B: only the broken path's button is named. Verified on 11.12.2: 1 error before, 0 after", "refs": ["mendixlabs/mxcli#1317", "mendixlabs/mxcli#1140"], "ce": ["CE0115"]}
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ primitive type"). mxcli knows a variable is a list when it is a list parameter,
a `create list`, a list retrieve or a list operation's result. Both work in
microflows and nanoflows.

`set` on an **object** variable is refused (MDL-SET01 in `check`; `check --references` and `exec` also catch an object from an association retrieve):
Mendix has no action that reassigns an object variable, and a Change variable on
one is the same CE7247. To walk a chain (`$Cursor = $Next` in a `while` loop),
write a sub-microflow that **returns** the next object and recurse; to change the
object itself, use `change $Obj (…)`.

### One statement per activity

Every list operation and aggregate is **one Studio Pro activity**, and it is
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Fixed

- **`set $Obj = …` on an object variable is refused** (mendixlabs/mxcli#1323) — with both variables single objects (e.g. a Reference retrieved from its FROM entity), `set $Cursor = $Next;` passed `check --references` and `exec` and was written as a Change variable action, which mxbuild refuses with CE7247 "Variable 'Cursor' does not have a primitive type". Mendix has no action that reassigns an object variable, so `check` (MDL-SET01, for the objects it can see without a project), `check --references` and `exec` now refuse it and name the alternatives (a sub-microflow that returns the next object, `change $Obj (…)`). `check --references` now types a Reference retrieve from its FROM entity as the object it is. `set` on a list variable stays a Change list Replace (ako/mxcli#949).
- **`RENAME JAVA ACTION` renames the Java class, not just the file** — renaming `M.JA_Old` to `JA_New` moved the source to `JA_New.java` but left `public class JA_Old`, its constructor and its `toString` inside it, which javac rejects (`class JA_Old is public, should be declared in a file named JA_Old.java`). A full build hid it, because mxbuild regenerates the stub in its own copy; `run --local --watch` hot reload and IDEs compile the file on disk. Those three places now follow the rename exactly as mxbuild rewrites them; user code and extra code are left as written. Applies to `mxcli rename java-action` too.
- **`run --local --watch` starts on Mendix 10.24 and 11.6** — it waited the full web-client timeout (5 minutes) for a bundle that had finished in seconds, then failed with `web client watcher timed out`. The rollup runner those versions ship reports its status as `{"code":"SUCCESS"}` rather than the modern-web-bundler protocol mxcli was reading; both are now understood, including that runner's error reports.
- **`run --local --watch` no longer loses a change made while the app boots** — the watch loop took its baseline after the boot, so a model written during the ~15 s boot was never built and the app kept serving the model from before it. The baseline is now the source time the boot build was made from, so that change is built on the first tick.
Expand Down
5 changes: 4 additions & 1 deletion cmd/mxcli/syntax/features_microflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,10 @@ func init() {
"DECLARE $Var Type = expression;\n" +
"$Var = expression; -- assign; SET is optional\n" +
"$Var/Attribute = expression;\n" +
"SET $Var = expression; -- same statement, explicit form",
"SET $Var = expression; -- same statement, explicit form\n" +
"-- $Var must be a primitive or a list (a list is a Change list Replace).\n" +
"-- An OBJECT variable cannot be reassigned (CE7247, MDL-SET01): return the\n" +
"-- new object from a sub-microflow, or `change $Obj (…)` its members.",
Example: "DECLARE $Count Integer = 0;\nDECLARE $Name String;\nset $Count = $Count + 1;\nset $Name = 'Hello';\nSET $Order/Status = 'Pending';",
SeeAlso: []string{"microflow.object-operations"},
})
Expand Down
23 changes: 23 additions & 0 deletions docs-site/src/appendixes/error-messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,29 @@ $n = count $Approved;

The same applies to both operands of `union`/`intersect`/`subtract`.

### MDL-SET01: `set` on an object variable

```
cannot set object variable '$Cursor' (G46.Group): Mendix has no action that reassigns an
object variable — Change variable takes only a primitive, and mxbuild rejects it with
CE7247 "Variable 'Cursor' does not have a primitive type". [MDL-SET01]
```

**Cause:** `set $Var = …` is a Change variable activity, which takes only a primitive
variable; on a list it is a Change list Replace. Mendix has no activity that reassigns an
object variable, so the statement used to be written as a Change variable and fail the build
with CE7247 (mendixlabs/mxcli#1323). It comes up naturally when walking a parent chain with
`set $Cursor = $Next` in a `while` loop. On a parameter mxbuild words the same code
`"Parameter 'X' cannot be changed."`.

Plain `check` reports it for the objects it can see without a project — entity parameters,
`create`, `retrieve … first`, loop iterators, casts, `head`. Whether an association retrieve
is an object or a list depends on the association, so that case is reported by
`check --references` and `exec`.

**Solution:** Return the new object from a sub-microflow (recursion for a chain walk),
retrieve it into a new variable, or change the object's members with `change $Obj (…)`.

### MDL-EMAIL02 / 03: A `send email` setting Studio Pro would not allow

```
Expand Down
Loading
Loading