diff --git a/.claude/skills/fix-issue/findings/mdl-executor.jsonl b/.claude/skills/fix-issue/findings/mdl-executor.jsonl index ee4a3065d..08ea896a5 100644 --- a/.claude/skills/fix-issue/findings/mdl-executor.jsonl +++ b/.claude/skills/fix-issue/findings/mdl-executor.jsonl @@ -246,253 +246,253 @@ {"area": "mdl/executor", "date": "2026-08-31", "raw": "| An `image` widget mxcli wrote shows nothing, and `mx check` fails with **\"No image selected.\"**; and a `describe` → rename → `exec` copy of an Atlas layout (or any page with a brand image) comes out with the image gone, describe having emitted `image staticImage1 (Responsive: false)` with no reference at all | `mdl/executor/widget_engine.go` (the `image` operation), `mdl/executor/widget_defs.go` + `mdl/executor/widget_registry.go` (`operationForType`, `defaultKnownOperations`), `mdl/backend/widgetobj/builder.go` (`SetImage`, `setImageValue`), `mdl/backend/mutation.go` + `mdl/backend/mcp/widget.go`, `{modelsdk,sdk}/widgets/definitions/image.def.json` (`imageObject` → `Image`), `mdl/executor/cmd_pages_describe_pluggable.go` (`extractCustomWidgetPropertyImage`) + `cmd_pages_describe_output.go` (`describeImageWidgetProps`) | MDL had no spelling for **which** image an image widget shows, so its default source (`ImageType: image`, an image collection entry) could never be satisfied and describe had nothing to emit — mxcli-formula1 FINDINGS §142. The name is three parts like an icon reference, `Module.Collection.Image`, stored as a plain string on the `WidgetValue`'s `Image` key. Both halves are needed: with only the write, a round trip still loses it. `setImageValue` **replaces** the existing `Image` key and never adds one — adding a key the widget definition does not declare is the CE0463 shape. Controls: stub `setImageValue` → `TestSetImageValue_SetsTheQualifiedName` fails; stub the describe emission → `TestDescribeImageWidget_EmitsTheImageReference` fails |", "ce": ["CE0463"]} {"area": "mdl/executor", "date": "2026-08-31", "raw": "| `image imgTypo (Image: 'MyFirstModule.Images.NoSuchImage')` passes `mxcli check --references` (\"Check passed!\") and the build then fails **CE1613** \"The selected image '…' no longer exists.\" | `mdl/executor/validate_widget_image_ref.go` (new, `imageRefErrors`, `buildImageQualifiedNames`), `mdl/executor/helpers.go` (`widgetRefCollector.images`, `validateWidgetReferences`) | Wiring `Image:` created a new qualified name and nothing resolved it — every other name a widget carries (microflow, nanoflow, page, snippet, entity) already was. This one has **three** parts, so the two mistakes get two messages: a missing collection sends the reader to `show image collections`, a missing image to that collection's contents (which the diagnostic lists). A **nil** name set means the collections could not be read and nothing is reported; an empty non-nil one is a real answer. No same-script exemption — MDL cannot create an image collection entry, so the reference can only resolve against the project. Control: `TestImageRefErrors_KnownImageIsClean`, and `TestImageRefErrors_NoCollectionsMeansNoOpinion` for the failed-read direction |", "ce": ["CE1613"]} {"area": "mdl/executor", "date": "2026-08-31", "symptom": "`main` goes red on a test that passed in **both** PRs that touched it — here `TestDescribeWorkflow_NoAnnotationEmitsNoComment`: `unexpected violations [MDL-WF05] for a plain jump`", "cause": "Two PRs merged in sequence. One added a validator rule (MDL-WF05, dangling `jump to` target); the other's control test asserted `len(violations) == 0` over a fixture that was **not a valid workflow** — a lone jump whose target did not exist. Each CI run was green because neither saw the other's change", "file": "`mdl/executor/issue1007_annotation_emit_test.go` (the fixture), `mdl/executor/validate_workflow_jump.go` (the rule, which is right)", "insight": "**A test fixture that is not a valid instance of the thing under test is a landmine for the next rule.** \"No violations at all\" is only meaningful over input that *should* have none; over an invalid fixture it silently asserts \"no rule has been written yet that notices this\". Fix the **fixture**, not the rule. Generalise: when a test asserts the ABSENCE of diagnostics, make the input something you would be happy to ship. Two green PRs can still merge to red and **neither PR's CI can detect it** — the only protection is a fixture that does not depend on which rules exist today. Found by running `make test` on an unrelated docs branch cut from the merged main, which is an argument for doing that on any branch cut after a batch merge.", "refs": ["#350", "#351"], "rules": ["MDL-WF04", "MDL-WF05"]} -{"area": "mdl/executor", "symptom": "A generated loop box is drawn far wider than the activities inside it (e.g. 880px around 440px of content), leaving a large empty area — \"the visualization is poor\"", "cause": "`measureStatements` sums each element's full width **and** adds `HorizontalSpacing` between them, but `HorizontalSpacing` is a centre-to-centre *pitch*: the builder centres each activity on `posX` and advances by exactly that. Counting it on top of each width over-measures a run of n simple activities by `(n-1)*ActivityWidth`", "file": "`mdl/executor/layout.go` (`measureStatementsSpan`), used by `addLoopStatement`/`addWhileStatement` in `cmd_microflows_builder_control.go`", "insight": "For a run of only simple activities the true span is `(n-1)*HorizontalSpacing + ActivityWidth`. **Do not guess the advance for a compound element** (IF/split, nested loop) — its `posX` advance comes from merge geometry (`mergeX + MergeSize + HorizontalSpacing/2`), and guessing under-sizes the box so activities land *outside* it, which is worse than a box that is too wide; those runs fall back to the conservative measure. Verify with a containment check: every child of a LoopedActivity must lie within `[0, width] × [0, height]`. Issue #790", "refs": ["#790"]} -{"area": "mdl/executor", "symptom": "`create or modify external entities` silently resets a per-entity setting the user had changed (e.g. allow-create-change-locally)", "cause": "`applyExternalEntityFields` stamps every field on both the create and the update path, so anything not derivable from the OData contract was overwritten with a default", "file": "`mdl/executor/cmd_contract.go` (`applyExternalEntityFields`)", "insight": "Separate contract-derived fields (Countable/Creatable/Deletable/Skip/Top — refresh from metadata) from local modelling choices (CreateChangeLocally — leave alone; a new entity arrives zero-valued, which is Mendix's default). Issue #782", "refs": ["#782"]} -{"area": "mdl/executor", "symptom": "`describe page` reports the wrong context entity under a data container bound to a microflow/nanoflow — `-- Context: $currentObject (Module.GetOrders)` names the *flow* instead of the entity it returns", "cause": "`widget.EntityContext = widget.DataSource.Reference` is correct for a database source (reference *is* the entity) and wrong for a flow source (reference is the flow's qualified name)", "file": "`mdl/executor/cmd_pages_describe_flowcontext.go` (`dataSourceEntityContext`, `flowReturnEntity`) + the five assignment sites in `cmd_pages_describe_parse.go`", "insight": "Resolve the flow's return type via `ListMicroflows`/`ListNanoflows` + `getHierarchy().GetQualifiedName`, taking the entity from an Object/List return type; fall back to the reference when the flow is unresolvable or returns a scalar, so the result is never worse than before. Note `GetRawUnitByName` is unimplemented on the modelsdk engine — the list+hierarchy path is the one that works"} -{"area": "mdl/executor", "symptom": "`describe page` omits a **pluggable** widget's `DataSource` when it is bound to a microflow (`datagrid g1 {` with no DataSource), while a `database from` source describes fine — re-applying the output recreates the grid unbound", "cause": "A `Forms$MicroflowSource` stores the name in the nested `Forms$MicroflowSettings` (`MicroflowSettings` → `Microflow`), which is what the write path and Studio Pro emit; the reader looked up a top-level `Microflow` key, got `\"\"`, and returned no datasource. The describe *formatter* was correct all along — read bug only", "file": "`mdl/executor/cmd_pages_describe_pluggable.go` (`microflowSourceRef`, `nanoflowSourceRef`, `extractDataGrid2DataSource`, `extractGalleryDataSource`, `parseCustomWidgetDataSource`)", "insight": "Read the nested settings with a top-level fallback, via one shared helper — there were four divergent copies of this lookup and two were wrong. `Forms$NanoflowSource` was missing entirely from the DataGrid2/Gallery switches; add it alongside. Do **not** touch `CustomWidgets$CustomWidgetNanoflowSource` (a different metamodel type whose `Nanoflow` really is top-level) or the `Forms$MicroflowAction` reads (actions, not datasources). Repro `mdl-examples/bug-tests/795-datagrid-microflow-datasource-describe.mdl`. Issue #795", "refs": ["#795"]} -{"area": "mdl/executor", "symptom": "`ALTER SETTINGS` / `CREATE CONFIGURATION` prints \"Updated …\" but `DESCRIBE SETTINGS` shows the old value — an Integer property was given a non-numeric value, or a Boolean anything other than `true`", "cause": "`strconv.Atoi`'s error was discarded (`if v, err := …; err == nil`) so the assignment was skipped while the caller still printed success; the boolean form compared against `\"true\"`, silently mapping every other spelling to false", "file": "`mdl/executor/cmd_settings.go` (`settingsInt`, `settingsBool`) + `mdl/executor/validate_settings.go` (`typedSettingsKeys`, MDL-SET01/MDL-SET02)", "insight": "Parse through a helper that returns a validation error naming the setting and the offending value, and register the property in `typedSettingsKeys` so `mxcli check` and the LSP flag it before the project is opened for writing. `TestTypedSettingsKeys_MatchExecutor` guards the table against drifting from the executor's switch. Repro `mdl-examples/bug-tests/805-alter-settings-typed-values.fail.mdl`. Issue #805", "refs": ["#805"], "rules": ["MDL-SET01", "MDL-SET02"]} -{"area": "mdl/executor", "symptom": "Retrieve/datasource XPath with a `[%…%]` token (e.g. `[System.owner = '[%CurrentUser%]']` or `[Title = '[%CurrentUser%]']`) fails `mx check` CE0161, but `[Title='abc']` is clean", "cause": "NOT a token-storage bug — tokens store intact and a type-valid token (`[DueDate < '[%CurrentDateTime%]']`) passes. The failures are semantically-invalid XPath: (1) String/scalar attr compared to a User token = type mismatch; (2) `System.owner`/`changedBy`/… referenced on an entity that doesn't store it (needs `alter entity X add attribute owner: autoowner`)", "file": "`mdl/executor/validate.go` (`validateRetrieveConstraints`, `baseSystemMemberRe`) — diagnose with `mxcli bson dump --type microflow` + `mx check`; verify the token alone works", "insight": "Don't \"fix\" storage — it's correct. Add a `--references` check: collect retrieve `(entity, constraint)` in `flowRefCollector`, look up the entity via `buildEntityIndex` (`ListDomainModels`), and flag a base-entity `System.` ref (regex excludes `/`-traversed refs) when the entity flag (`HasOwner` etc.) is off, with the `alter entity … add attribute …: auto…` hint. Same-script-created entities aren't in the project index, so the check only fires against existing project entities. **Also fixed**: a bare `[%token%]` *inside* a bracketed constraint (`[DueDate < [%CurrentDateTime%]]`) stored unquoted (the inline path keeps the raw source) → CE0161. `normalizeXPathTokens` (`mdl/visitor/visitor_page_v3.go`) requotes bare tokens; wired into `buildXPathSourceExpression`, the multi-predicate `predicateSources`, `buildXPathString`, and `bracketedXPathFromExpr` (already-quoted tokens untouched). Issue #641", "refs": ["#641"], "ce": ["CE0161"]} -{"area": "mdl/executor", "symptom": "`describe` shows `$var = action ...;`", "cause": "Missing formatter case", "file": "`mdl/executor/cmd_microflows_format_action.go` → `formatActionStatement()`", "insight": "Add `case *microflows.XxxAction:` with `fmt.Sprintf` output"} -{"area": "mdl/executor", "symptom": "`describe` shows `$var = list operation %T;` (with type name)", "cause": "Missing formatter case", "file": "`mdl/executor/cmd_microflows_format_action.go` → `formatListOperation()`", "insight": "Add `case *microflows.XxxOperation:` before the `default`"} -{"area": "mdl/executor", "symptom": "A `create page` reported success but the built page is EMPTY — every widget gone — and `mx check` fails with CE1613 \"The selected layout 'dummyModule.dummyName' no longer exists\"", "cause": "The page had no `Layout:` clause, so `buildPageV3` created no `LayoutCall`; the widget tree is built into the LayoutCall's placeholder arguments, so with no LayoutCall the widgets have nowhere to attach and are silently dropped. `dummyModule.dummyName` is *Mendix's* placeholder for a missing layout, not something mxcli writes", "file": "`mdl/executor/cmd_pages_builder_v3.go` (`buildPageV3`, the `if page.LayoutCall != nil` block)", "insight": "Reject a page that has body widgets (or placeholder blocks) but no LayoutCall — distinguish \"no Layout: clause\" from \"layout not found\" in the message. A Mendix page always needs a layout; snippets (buildSnippetV3) are layout-less and unaffected. Repro `mdl-examples/bug-tests/266-page-without-layout-drops-widgets.mdl`", "ce": ["CE1613"]} -{"area": "mdl/executor", "symptom": "`mx check` fails to LOAD the project — `StorageLoadException: ... 'Module.Name' is not a valid AttributeIdentifier` after a `create`/`change` with a `Module.Assoc = …` member, yet `mxcli exec` reported success", "cause": "A one-qualifier member (`Module.Name`) that isn't a known association was written as an *attribute* ref, but a one-qualifier name can't be a valid attribute (attributes are bare or `Module.Entity.Attribute`) → unloadable .mpr. Usually the association's `create` failed earlier (non-idempotent) leaving it absent", "file": "`mdl/executor/cmd_microflows_builder_actions.go` (`resolveMemberChange`, the \"Not an association in the authored module\" branch)", "insight": "When the domain model is available and the one-dot member isn't in `dm.Associations`/`dm.CrossAssociations`, `fb.addError` with an actionable \"create the association first\" message instead of writing an Attribute. Same-script associations are visible via `GetDomainModel`, so no false positive. Repro `mdl-examples/bug-tests/264-create-member-unknown-association.mdl`. FINDINGS #51", "refs": ["#51"]} -{"area": "mdl/executor", "symptom": "`mxcli check` rejects a **valid** microflow: **MDL048** on `retrieve … where [id = '[%CurrentUser%]']` (the standard signed-in-user idiom) — but `mx check` → 0 errors", "cause": "MDL048 targets constraining `id` against a STORED value (String/Long var or plain literal), which Mendix XPath can't do; it also matched the `'[%CurrentUser%]'` **server token**, which Mendix DOES resolve to a GUID", "file": "`mdl/executor/validate_microflow.go` (`checkXPathIdConstraint`)", "insight": "Skip an operand of the form `'[%…%]'` (a resolved token) before flagging. Case still fires for real stored-id values. Test `TestValidateMicroflow_XPathIdConstraint` (CurrentUser case); repro `mdl-examples/bug-tests/52-53-microflow-check-false-positives.mdl`. FINDINGS #53", "refs": ["#53"], "rules": ["MDL048"]} -{"area": "mdl/executor", "symptom": "`mxcli check` rejects a **valid** microflow: **MDL045** (\"`/` is division\") on `round($a div $obj/Attr * 100)` — division whose divisor is an association-attribute path — but `mx check` → 0 errors", "cause": "The MDL grammar parses `div`/`*`/`/` at one precedence level, so `$a div $obj/Attr` mis-nests as `($a div $obj) / Attr`; MDL045 saw the `/ Attr` as division. But `Attr` is a bare member name — Mendix has no `/` division operator and re-parses the raw `$obj/Attr` as a path (serialized output preserves the `/`, so the build is clean)", "file": "`mdl/executor/validate_microflow.go` (`exprHasSlashDivision`)", "insight": "Don't flag a `/` BinaryExpr whose RIGHT operand is a bare `IdentifierExpr` (member navigation); real division has a numeric/paren/variable divisor. Test `TestValidateMicroflow_SlashDivision` (div-by-assoc cases); repro `mdl-examples/bug-tests/52-53-microflow-check-false-positives.mdl`. FINDINGS #52", "refs": ["#52"], "rules": ["MDL045"]} -{"area": "mdl/executor", "symptom": "`create association X …` errors \"association already exists\" on re-run and aborts the script", "cause": "Correct SQL-shaped semantics (like `CREATE TABLE`) — `create` is not idempotent. The idempotent form is `create or modify association`, but it was undiscoverable from the bare error", "file": "`mdl/executor/cmd_associations.go` (the `NewAlreadyExists(\"association\", …)` sites)", "insight": "Not a code bug in the write path — improve the error to name `create or modify association …` and `drop association …`. Repro `mdl-examples/bug-tests/51-create-or-modify-association.mdl`. FINDINGS #51", "refs": ["#51"]} -{"area": "mdl/executor", "symptom": "`dynamictext x (Content: '')` builds with **CE0720** \"Place holder index 1 is greater than 0\" — `mxcli check` ✓, describe shows `Content: '{1}'` with no params", "cause": "The builder unconditionally defaulted empty content to the template `{1}`, creating a placeholder with no matching parameter (orphaned)", "file": "`mdl/executor/cmd_pages_builder_v3_widgets.go` (`buildDynamicTextV3`, final `content == \"\"` guard)", "insight": "Only default to `{1}` when there IS a parameter (`autoGeneratedParams`/`explicitParams`); empty content with no params is a literal empty template. Test `TestBuildDynamicTextV3_EmptyContent`; repro `mdl-examples/bug-tests/traceops-9-10-17-dynamictext-listview.mdl`. traceops #9", "refs": ["#9"], "ce": ["CE0720"]} -{"area": "mdl/executor", "symptom": "`dynamictext s (Content: '$318')` builds with **CE0402/CE1613** (\"attribute '$318' no longer exists\") — the literal was turned into an unbound `{1}` param", "cause": "The auto-bind check treated ANY `$`-prefixed content as a variable; `$318` (dollar + digits) is not a valid Mendix variable", "file": "`mdl/executor/cmd_pages_builder_v3_widgets.go` (`isDynamicTextVariableRef` / `dynamicTextVariableRe`)", "insight": "Treat `$` as a variable ONLY when followed by a letter/underscore (`^\\$[A-Za-z_]`); `$318` stays literal content. Tests `TestBuildDynamicTextV3_DollarDigitLiteral`, `TestIsDynamicTextVariableRef`. traceops #10", "refs": ["#10"], "ce": ["CE0402", "CE1613"]} -{"area": "mdl/executor", "symptom": "`listview lv (… PageSize: 200)` always pages at 20 — `mxcli check` ✓, `mx check` ✓, describe shows no PageSize", "cause": "The property parsed into the AST but three layers ignored it: `buildListViewV3` hardcoded `PageSize: 20`, the describe parse never read it, and the listview describe formatter never emitted it", "file": "`mdl/executor/cmd_pages_builder_v3_widgets.go` (`buildListViewV3`) + `cmd_pages_describe_parse.go` (Forms$ListView case) + `cmd_pages_describe_output.go` (listview case)", "insight": "Read `w.GetIntProp(\"PageSize\")` on write; read `w[\"PageSize\"]` on describe; emit a non-default PageSize in the listview formatter. Test `TestBuildListViewV3_PageSize`. traceops #17", "refs": ["#17"]} -{"area": "mdl/executor", "symptom": "`combobox (Association: Mod.Ref, …)` drops the binding — `mxcli check` ✓ but MxBuild fails **CE0642** \"Property 'Attribute' is required\"", "cause": "The widget engine's `Association` source read the reference only from the `attribute:` keyword (`w.GetAttribute()`), so an explicit `Association:` keyword was ignored and the widget fell back to enumeration mode", "file": "`mdl/executor/widget_engine.go` (`case \"Association\"`) + `mdl/executor/validate_widgets.go` (`validateComboBoxAssociation`)", "insight": "Read the reference from `Association:` OR `attribute:`; and add MDL-WIDGET16 flagging an association combobox that lacks the required `datasource:` (option list). A complete association combobox needs reference + `datasource:` + `CaptionAttribute:`. Tests `TestValidateComboBoxAssociation`; repro `mdl-examples/bug-tests/traceops-23-combobox-association.mdl`. traceops #23", "refs": ["#23"], "ce": ["CE0642"]} -{"area": "mdl/executor", "raw": "| `grant view on page` / `grant execute on microflow\\|nanoflow` / `grant access on odata\\|published rest service` to a role from **another module** passes `mxcli check`/`exec` but fails the Mendix build with **CE0148 \"reselect roles\"** — the own-module role works | Document access (page/microflow/nanoflow/service `AllowedModuleRoles`) may only reference the document's **own** module roles; Studio Pro's picker only offers those. The grant path wrote `role.Module + \".\" + role.Name` verbatim with no same-module check (only `validateModuleRole` = role-exists-in-its-module), so a cross-module reference reached the model. The MOVE path already guarded this (`remapDocumentAccessRoles`) — GRANT didn't | `mdl/executor/cmd_security_defaults.go` (`checkDocumentAccessRolesSameModule`) + the 5 grant handlers in `mdl/executor/cmd_security_write.go` | Pre-check each grant: reject when any `role.Module != docModule` with an actionable message (name the doc's module + suggest the own-module role). Reject (don't silently remap) — a GRANT is explicit, so a wrong role/doc shouldn't be substituted. Wired into page/microflow/nanoflow/OData/published-REST grants. Repro `mdl-examples/bug-tests/ce0148-cross-module-grant.mdl` |", "ce": ["CE0148"]} -{"area": "mdl/executor", "symptom": "CE7054 \"parameters updated\" / CE7067 \"does not support body entity\" after `send rest request`", "cause": "`addSendRestRequestAction` emitted wrong BSON: all params as query params, BodyVariable set for JSON bodies", "file": "`mdl/executor/cmd_microflows_builder_calls.go` → `addSendRestRequestAction`", "insight": "Look up operation via `fb.restServices`; route path/query params with `buildRestParameterMappings`; suppress BodyVariable for JSON/TEMPLATE/FILE via `shouldSetBodyVariable`", "ce": ["CE7054", "CE7067"]} -{"area": "mdl/executor", "symptom": "`CREATE X` returns \"already exists — use create or replace to overwrite\" but OR REPLACE is not valid for that type", "cause": "Error message in executor points to wrong keyword", "file": "`mdl/executor/cmd__*.go` — find the `NewAlreadyExistsMsg` call", "insight": "Change hint from `or replace` to `or modify`; verify the AST stmt uses `CreateOrModify` not `CreateOrReplace`"} -{"area": "mdl/executor", "symptom": "`mx check` CE0126 \"Missing value for parameter X\" on `call java action ... ($Param = empty)` for typed (non-entity, non-microflow) parameters", "cause": "Builder emitted `BasicCodeActionParameterValue.Argument: \"\"` instead of the literal `\"empty\"` keyword", "file": "`mdl/executor/cmd_microflows_builder_calls.go` → `addCallJavaActionAction`", "insight": "Capture all resolved BasicParameterType params into `resolvedBasicParams`; when bound to MDL `empty`, emit `Argument: \"empty\"` so Studio Pro recognises an explicit empty literal rather than treating the slot as missing", "ce": ["CE0126"]} -{"area": "mdl/executor", "symptom": "`DESCRIBE microflow` puts shared activities inside an `if … then` block — they should appear after `end if;`", "cause": "Nested guard split inside `traverseFlowUntilMerge` crosses the outer merge boundary", "file": "`mdl/executor/cmd_microflows_show_helpers.go` — guard path in `traverseFlowUntilMerge` (~line 854)", "insight": "Add `if contID != mergeID` guard before the `isMerge` skip-through so the guard continuation never crosses the outer merge"} -{"area": "mdl/executor", "symptom": "Published OData service: `mx check` reports **CE5016** \"Attribute X has type String(50), but is published as .\" (empty published type) for every exposed attribute — modelsdk engine only (legacy slides by)", "cause": "mxcli never wrote `ODataPublish$PublishedAttribute.EdmType`, the OData EDM type Studio Pro stores on every published attribute. Both engines omitted it, but legacy's `$Type` field order let Studio Pro recompute; modelsdk's order tripped the check. **The legacy output was non-canonical too — don't treat \"legacy passes mx check\" as \"legacy is correct.\"** Found by diffing mxcli BSON vs a Studio-Pro-duplicated-and-fixed copy (the definitive method for these \"empty field\" checks)", "file": "`mdl/executor/cmd_odata.go` (`mendixAttrTypeToEdm`, `lookupEntityMembers`, `astEntityDefToModel`) + both writers (`publishedMemberToGen`, `serializePublishedMember`) + both reads", "insight": "Add `EdmType` to `model.PublishedMember`; derive it from the attribute's Mendix type (inverse of `edmToDomainModelAttrType`: String→Edm.String, Integer→Edm.Int32, Long/AutoNumber→Edm.Int64, Decimal→Edm.Decimal, Boolean→Edm.Boolean, DateTime→Edm.DateTimeOffset, Binary→Edm.Binary); emit + read in both engines", "ce": ["CE5016"]} -{"area": "mdl/executor", "symptom": "Published OData service: `mx check` reports **CE5022** \"Published association X has changed multiplicity\" for every exposed association — modelsdk engine only", "cause": "Same shape as CE5016 but for `ODataPublish$PublishedAssociationEnd.IsMany` (the exposed navigation's multiplicity), which mxcli never wrote. Confirmed via the same Studio-Pro-duplicate-and-diff method", "file": "`mdl/executor/cmd_odata.go` (`assocMembership.Type`, `astEntityDefToModel`) + both writers/reads", "insight": "Add `IsMany` to `model.PublishedMember`; compute from the association type + exposed side (ReferenceSet ⇒ to-many either end; Reference ⇒ to-many only from the TO/Child side, to-one from the FROM/Parent side); emit + read in both engines", "ce": ["CE5016", "CE5022"]} -{"area": "mdl/executor", "symptom": "MDL widget property `mxcli check`s clean but Studio Pro renders the default (e.g. `dataview ... (FormOrientation: Vertical)` always Horizontal)", "cause": "V3 grammar generic-property branch parks the value in `w.Properties`, but the V3 builder never reads it and the writer never emits it; the widget struct has no field for it", "file": "`mdl/executor/cmd_pages_builder_v3_widgets.go` (`buildXxxV3`) + `sdk/mpr/writer_widgets_*.go` (`serializeXxx`) + `sdk/pages/pages_widgets_*.go`", "insight": "Add field to `pages.Xxx`; read via `w.GetStringProp` / `w.GetIntProp`; write in `serializeXxx`. If the Studio Pro UI label differs from the BSON storage name (e.g. DataView \"Form Orientation\" → `LabelWidth: 0/N`), confirm by diffing a Studio Pro-saved page against the reflection-data defaults"} -{"area": "mdl/executor", "symptom": "Pluggable widget datasource property (`optionsSourceAssociationDataSource: Module.Entity`) passes `check` + `exec` but `mx check` reports CE0642 \"Property 'Entity' is required\"", "cause": "A `datasource`-operation property was authored by name; the engine reads the widget's `datasource:` clause (`Properties[\"DataSource\"]`), not the named key, so the value is silently dropped (and `hasDataSource` mode-selection also misfires)", "file": "`mdl/executor/validate_widgets.go` (`datasourceTypedKeys`) — and author via the `datasource:` clause", "insight": "`check` now rejects named datasource-typed props (MDL-WIDGET05); the fix for the user is `datasource: database Module.Entity`. Persisting a *named* datasource property (multi-datasource widgets) needs a Studio-Pro-verified mapping — deferred. Real-but-unmapped props get MDL-WIDGET06 warning via def.json `knownProperties` (not a false MDL-WIDGET01)", "ce": ["CE0642"]} -{"area": "mdl/executor", "symptom": "CE7247 \"The name 'X' is a reserved word.\" on non-persistent entity attributes (Owner/Type/Context/Id/CreatedDate/ChangedDate/ChangedBy) — mxcli accepts the MDL silently, Studio Pro rejects the project", "cause": "`ValidateEntity` early-returned for NPEs; reserved-word check was not wired into the executor", "file": "`mdl/executor/cmd_enumerations.go` (`ValidateEntity`) and `mdl/executor/cmd_entities.go` (`execCreateEntity`)", "insight": "Drop the `EntityPersistent` early-return; gate only `mendixSystemAttributeNames` (MDL020) to persistent; run `mendixReservedWords` (MDL021) for all kinds; call `ValidateEntity` from `execCreateEntity` before any backend write. Issue #552", "refs": ["#552"], "ce": ["CE7247"], "rules": ["MDL020", "MDL021"]} -{"area": "mdl/executor", "symptom": "`DESCRIBE MICROFLOW` (mdl/json) times out at 300s on a high-McCabe flow, but `--format mermaid` renders in ~1s — extraction is fast, the serializer hangs", "cause": "Exponential path enumeration: `duplicateOutputVariableWarnings` (run during `formatMicroflowActivities`) walked EVERY execution path, cloning the visited map at each branch (`cloneIDBoolMap`), to find output vars assigned twice on one path — O(2^branches). ~20 sequential `if/end if` diamonds already took ~10s. Diagnose with `DBGPROF=… ` CPU profile / add a pprof block to `describeMicroflow`; top-cum names the offender (not the describe traversal, which is linear)", "file": "`mdl/executor/cmd_microflows_show.go` (`duplicateOutputVariableWarnings`)", "insight": "Replace the all-paths walk with **reachability**: a name is a duplicate iff two of its assignments are path-ordered (one reaches the other); exclusive-branch assignments never reach each other. Memoized `reachableFrom` is O(V·E). Loop bodies inherit names assigned by activities that reach the loop node. Went 9.5s→0.1s at 20 diamonds; 120 diamonds completes instantly. Issue #710", "refs": ["#710"]} -{"area": "mdl/executor", "symptom": "`DESCRIBE` of an entity/module emits `grant ... (read (Module.Entity.Attr))` that fails to re-parse with `mismatched input '.'` — breaks the DESCRIBE roundtrip", "cause": "Member emitter used the fully-qualified BY_NAME reference; grant grammar accepts a bare `IDENTIFIER` only", "file": "`mdl/executor/cmd_entities_access.go` → `resolveEntityMemberAccess`", "insight": "Strip `memberName` to the last `.`-segment before appending (bare names have no dot, so it's a no-op for them). Issue #633", "refs": ["#633"]} -{"area": "mdl/executor", "symptom": "`style:` on a `dynamictext` crashes MxBuild with a NullReferenceException (fails at build, not check)", "cause": "The generic appearance applier wrote an inline Style into the DynamicText's `Forms$Appearance`; Mendix's metamodel can't handle it", "file": "`mdl/executor/cmd_pages_builder_v3.go` (`applyWidgetAppearance`) + `mdl/executor/validate_widgets.go` (`validateStaticWidget`)", "insight": "Reject an inline `style` on a dynamictext as MDL-WIDGET03 (check + executor); the workaround is to wrap it in a container and style the container. Issue #673", "refs": ["#673"]} -{"area": "mdl/executor", "symptom": "`DYNAMICTEXT (Attribute: X)` (e.g. inside a LISTVIEW) is silently dropped — `describe`/BSON shows `Content: '{1}'` with no parameter binding; Studio Pro throws `System.NullReferenceException` (ClientTemplateFormPart.CollectControls) and MxBuild fails CE0720 \"Place holder index 1 is greater than 0\"", "cause": "`buildDynamicTextV3` read `Content`/`ContentParams` but never read the `Attribute:` property, so the binding was discarded and the template defaulted to the orphaned \"{1}\". `mxcli check` had no orphan detection", "file": "`mdl/executor/cmd_pages_builder_v3_widgets.go` (`buildDynamicTextV3`) + `mdl/executor/validate_widgets.go` (`validateStaticWidget`/`validateDynamicTextPlaceholders`)", "insight": "Treat `Attribute: X` as a single auto-generated template param (route through `resolveTemplateAttributePathFull`, same as `ContentParams: [{1} = X]`; non-String attrs get `toString()`). Add **MDL-WIDGET04**: flag a dynamictext whose `Content` template references `{N}` with fewer than N bound params (counting ContentParams or a single Attribute). Issue #650", "refs": ["#650"], "ce": ["CE0720"]} -{"area": "mdl/executor", "symptom": "A widget `show_page` whose target page is created later in the same script passes `mxcli check --references` but the executor fails with \"page not found … defined later in this script\"", "cause": "The whole-script scriptContext used by reference validation tolerates forward refs (the page exists *somewhere*); the executor resolves page refs in statement order", "file": "`mdl/executor/validate.go` (`validateForwardPageRefs`)", "insight": "An ordered pass flags a widget page ref that is neither in the project nor created earlier in the script, with the same \"move the create statement earlier\" hint the executor gives — keeping check consistent with execution. Issue #674", "refs": ["#674"]} -{"area": "mdl/executor", "symptom": "Workflow constructs pass `mxcli check` but MxBuild rejects them: user task without a page (CE1834); a single-outcome user task whose one outcome has a nested activity flow (CE1876); a decision outcome that isn't a valid enum value identifier, e.g. `'Confirmed closed'`", "cause": "`CreateWorkflowStmt` had **no** case in `validateWithContext` and no `ValidateWorkflow` at all — workflows received zero semantic validation", "file": "`mdl/executor/validate_workflow.go` (`ValidateWorkflow`, wired from `cmd/mxcli/cmd_check.go`)", "insight": "Recursively walk `stmt.Activities`: **MDL-WF01** flag a `WorkflowUserTaskNode` with empty `Page`; **MDL-WF02** flag `len(Outcomes)==1 && len(Outcomes[0].Activities)>0`; **MDL-WF03** flag a decision/call-microflow outcome `Value` that isn't `True`/`False`/`Default` and fails the identifier regex (space/punctuation). Syntax-only (no project). The page-context-entity check (CE7412) and enum-membership form of WF03 are `--references` follow-ups. See `PROPOSAL_check_mxbuild_gap_heuristics.md`", "ce": ["CE1834", "CE1876", "CE7412"], "rules": ["MDL-WF01", "MDL-WF02", "MDL-WF03"]} -{"area": "mdl/executor", "symptom": "A DataGrid **control-bar** button passing `$currentObject` (e.g. `Action: show_page P (Order: $currentObject)`) passes `mxcli check` but MxBuild fails CE1571 \"No argument has been selected for parameter …\"", "cause": "A control bar sits above the grid and is not row-scoped, so `$currentObject` is unbound there; no check distinguished control-bar buttons from row-scoped (column) buttons", "file": "`mdl/executor/validate_page_button_context.go` (`ValidatePageButtonContext`, wired from `cmd/mxcli/cmd_check.go`)", "insight": "**MDL-BUTTON01**: walk the widget tree tracking an `underControlBar` flag (any `controlbar`-typed ancestor, mirroring `checkLayoutGridTree`'s `underGrid`); flag any button action (and its `ThenAction` chain) whose args contain the string `$currentObject`. Row-scoped column buttons are unaffected. Syntax-only", "ce": ["CE1571"]} -{"area": "mdl/executor", "symptom": "`DESCRIBE` of a page/snippet emits `action: call_microflow X` / `call_nanoflow X` (button/widget actions) that fails to re-parse — breaks the DESCRIBE roundtrip", "cause": "Action emitters prefixed `call_`; the page-action grammar (`actionExprV3`) accepts `MICROFLOW`/`NANOFLOW` only", "file": "`mdl/executor/cmd_pages_describe_output.go` (`extractButtonAction`) + `cmd_pages_describe_pluggable.go` (`extractCustomWidgetPropertyAction`)", "insight": "Emit `microflow `/`nanoflow ` without the `call_` prefix. Issue #634", "refs": ["#634"]} -{"area": "mdl/executor", "symptom": "`DESCRIBE` of a Java action (or `describe module … with all`) emits a body-less `create java action …;` that fails with `no viable alternative` and cascades into following statements — happens for add-on/Marketplace actions whose `.java` source isn't on disk", "cause": "Body emitted only when `readJavaActionUserCode` returns non-empty, but the grammar requires `AS DOLLAR_STRING` (body is mandatory)", "file": "`mdl/executor/cmd_javaactions.go` → `describeJavaAction`", "insight": "Always emit the `as $$ … $$;` block; when source can't be read, write a placeholder comment body. Issue #637", "refs": ["#637"]} -{"area": "mdl/executor", "symptom": "`DESCRIBE` of a page emits `column Title (...)` / `column Description (...)` (datagrid columns named after a reserved keyword) that fails with `missing {IDENTIFIER, QUOTED_IDENTIFIER}` — breaks the DESCRIBE roundtrip", "cause": "#619 added `mdlIdent` (quote-if-reserved) for general widget names but missed datagrid column names", "file": "`mdl/executor/cmd_pages_describe_output.go` (`column %s` header, ~line 837)", "insight": "Wrap the column name: `fmt.Sprintf(\"column %s\", mdlIdent(colName))`. Issue #638", "refs": ["#619", "#638"]} -{"area": "mdl/executor", "symptom": "`DESCRIBE` of a page/snippet emits widget-action microflow args as `Param = $value` that fails with `mismatched input '=' expecting ':'` — breaks the DESCRIBE roundtrip", "cause": "Arg emitters used `=`, but `microflowArgV3` accepts `IDENTIFIER COLON expr` (`Param: $value`) or `VARIABLE EQUALS expr` (`$Param = …`) — not `IDENTIFIER EQUALS`", "file": "`mdl/executor/cmd_pages_describe_output.go` → `extractMicroflowParameters` / `extractNanoflowParameters`", "insight": "Emit the canonical colon form: `paramName+\": \"+value`. Issue #640", "refs": ["#640"]} -{"area": "mdl/executor", "symptom": "DESCRIBE drops `DataSource: selection X` for a DataView bound to a gallery/listview selection (master-detail pages)", "cause": "`extractDataViewDataSource` only handled `Forms$MicroflowSource` / `Forms$NanoflowSource` / `Forms$DataViewSource` / `Forms$DatabaseSource`; `Forms$ListenTargetSource` fell through to `return nil`", "file": "`mdl/executor/cmd_pages_describe_parse.go` (`extractDataViewDataSource`) + `mdl/executor/cmd_pages_describe_output.go` (DataView case) + `mdl/executor/cmd_pages_describe.go` (rawDataSource doc-comment)", "insight": "Add `case \"Forms$ListenTargetSource\":` returning `{Type: \"selection\", Reference: ds[\"ListenTarget\"]}`; add `case \"selection\":` in the DataView output switch emitting `DataSource: selection `"} -{"area": "mdl/executor", "symptom": "CE0463 on a pluggable widget whose TextTemplate property is *conditionally hidden* by an enum/boolean toggle (VideoPlayer `videoUrl`/`posterUrl` when `type=expression`; Timeline `title`/`description`/`timeIndication` when `customVisualization=true`) — engine clones the template's populated `ClientTemplate` for a property Studio Pro hides and nulls", "cause": "Engine has no per-property visibility metadata; the hide rules live in the widget's compiled `editorConfig.js` (`hidePropertyIn`/`hidePropertiesIn`), not `widget.xml`", "file": "`mdl/executor/widget_defs.go` `widgetVisibilityRules` table + `mdl/backend/mpr/widget_builder.go` `ApplyPropertyVisibility`", "insight": "Extract the widget's `.mpk` `*.editorConfig.js` (`unzip` + grep `hidePropertiesIn`), transcribe the rule into `widgetVisibilityRules[widgetID]` as `{propertyKey, hiddenWhen:{propertyKey, operator eq/ne/truthy/falsy, value}}`; the engine nulls hidden TextTemplate-typed props at build time. Bump `WidgetDefGeneratorVersion` so stale project `.def.json` auto-refresh. Issue #574", "refs": ["#574"], "ce": ["CE0463"]} -{"area": "mdl/executor", "symptom": "CE0463 on a `datagrid` whose column uses `ColumnWidth: manual` + `Size: N` — Studio Pro resets the column `size` to `1`", "cause": "The MDL `ColumnWidth:` keyword isn't mapped to the schema `width` enum, so `width` stays at its `autoFill` default; `size` only applies when `width=manual`, so the value is inconsistent. Regression from the Stream B keyword-path consolidation (the deleted `datagrid_builder.go` did `colPropString(col.Properties, \"ColumnWidth\", \"autoFill\")`)", "file": "`mdl/executor/widget_defs.go` `itemPropertyAliases`", "insight": "Add the MDL→schema alias under `[datagrid][\"columns\"]`: `\"width\": {\"ColumnWidth\"}`. Bump `WidgetDefGeneratorVersion` so stale `.def.json` regenerate. General rule when a column/object-list property's MDL keyword differs from the `.mpk` schema key (not just case), add it to `itemPropertyAliases`; cross-check against the pre-B3 `datagrid_builder.go` `colProp*` calls for any other dropped mappings", "ce": ["CE0463"]} -{"area": "mdl/executor", "symptom": "`describe page` shows a widget's default placeholder text (e.g. the Dutch `'Tekst'` for a DynamicText) instead of the configured content; round-tripping describe→create then overwrites the real caption", "cause": "Text extraction was language-blind: `extractTextContent`/`extractTextCaption`/`extractTextFromTemplate` returned the **first** `Texts$Text` `Items[]` entry, and the page title hardcoded `GetTranslation(\"en_US\")`. In a multi-language project the first Items entry is often a non-default-language placeholder, so the wrong translation is shown. (The MDL surface is single-language regardless — this is display-only; note the real text isn't lost on disk, only mis-displayed)", "file": "`mdl/executor/describe_language.go` (new) + the three extractors in `cmd_pages_describe_output.go`/`cmd_pages_describe_parse.go` + title in `cmd_pages_describe.go`", "insight": "Select the translation by **project default language → en_US → first non-empty** via `selectTranslationText` / `pickTextTranslation`; get the default language from `ctx.Backend.GetProjectSettings().Language.DefaultLanguageCode`, cached on `executorCache` and pre-warmed in `preWarmCache` (race-free for parallel describe). No MDL bug-test possible (MDL can't author >1 translation) — covered by unit tests in `describe_language_test.go`. Issue #702", "refs": ["#702"]} -{"area": "mdl/executor", "symptom": "Mendix can't resolve the microflow named in `CREATE ODATA CLIENT (ConfigurationMicroflow: microflow X.Y)` / `ErrorHandlingMicroflow:` — error names the literal string `\"MICROFLOW X.Y\"` as the missing microflow", "cause": "Case-mismatched prefix strip: visitor emits uppercase `\"MICROFLOW \"` from `odataValueText`, but `extractMicroflowRef` only trimmed lowercase `\"microflow \"`, so the keyword survived into BSON", "file": "`mdl/executor/cmd_odata.go` → `extractMicroflowRef`", "insight": "Use a case-insensitive strip: `if strings.EqualFold(ref[:10], \"microflow \") { return ref[10:] }`. Whenever a value goes from a visitor that emits a keyword-prefixed form to an executor that strips it, the strip must match the case the visitor produces — grep visitor files for `\"MICROFLOW \" +`/`\"ENTITY \" +`/etc. when adding a new property. Issue #573", "refs": ["#573"]} -{"area": "mdl/executor", "symptom": "`create [or modify] association ... to System.X` passes `mxcli check --references` and `mxcli diff` but fails at `mxcli exec` with `child entity not found: System.X`", "cause": "Two divergent entity resolvers: the write path's `findEntity` resolved the owning module via `h.FindModuleID(dm.ID)`, but the virtual System domain model is not a real unit, so the hierarchy walk yielded an empty module name and System entities never matched. The validation path (`buildEntityQualifiedNames`) keyed on `dm.ContainerID` and worked, hence the check-passes/exec-fails split", "file": "`mdl/executor/oql_type_inference.go` → `findEntity`", "insight": "Resolve the module from `dm.ContainerID` (the module ID `BuildSystemDomainModel` sets), not by walking up from the DM's own unit ID. When a symptom is \"passes check/diff but fails exec,\" suspect two resolvers and make the write-path one match the validation-path one; add an `exec`-level test, not just a `check` test. Issue #610", "refs": ["#610"]} -{"area": "mdl/executor", "symptom": "`ALTER STYLING ON PAGE/SNIPPET ... SET ...` fails with `unsupported container type: PAGE` (and `DESCRIBE STYLING` silently shows \"No widgets found\"); even past that, design-property writes never reached builder-created pages", "cause": "Two layered bugs: (1) the visitor emits uppercase `ContainerType` `\"PAGE\"`/`\"SNIPPET\"` but `execAlterStyling`/`execDescribeStyling` compared lowercase, so it fell through to the unsupported-container error; (2) ALTER STYLING used the reflection walker `walkPageWidgets` (legacy `ListPages`/`UpdatePage`), which can't locate widgets in MDL-builder pages and violates the mutator-pattern rule", "file": "`mdl/executor/cmd_styling.go` (`execAlterStyling`, `execDescribeStyling`) + `mdl/backend/mpr/page_mutator.go`", "insight": "Normalise container type with `strings.ToLower`. Route ALTER STYLING through `ctx.Backend.OpenPageForMutation(unitID)` like ALTER PAGE; check `mutator.FindWidget`. Add `SetDesignProperty`/`RemoveDesignProperty`/`ClearDesignProperties` to the `PageMutator` interface, writing the widget's `Appearance.DesignProperties` BSON array (`Forms$DesignPropertyValue` → `Toggle`/`Option`/`Custom` value), preserving an existing custom kind on option updates. When an ALTER uses a container-type discriminator, mirror the casing fix already done for ALTER PAGE (#402). Issue #631", "refs": ["#402", "#631"]} -{"area": "mdl/executor", "symptom": "`declare $x list of T = empty;` (or any list-typed `declare`) passes `mxcli check` but Studio Pro rejects with CE0053 (\"type not allowed\") + CE0038 (\"value required\")", "cause": "`declare` maps to a Create Variable activity, which Mendix forbids from producing a list — but the validator only flagged an empty list *used as a loop source* (MDL002), never the declaration itself", "file": "`mdl/executor/validate_microflow.go` → `walkBody` `*ast.DeclareStmt` case", "insight": "Emit `MDL040` (SeverityError) for any `stmt.Type.Kind == ast.TypeListOf`, regardless of initializer. Lists must come from a microflow parameter, a `retrieve`, or `$x = create list of T;`. Also fix the synced skills that present declare-list as valid (`write-microflows.md`, `cheatsheet-variables.md`, `check-syntax.md`, `patterns-*`). Issue #607", "refs": ["#607"], "ce": ["CE0038", "CE0053"], "rules": ["MDL002", "MDL040"]} -{"area": "mdl/executor", "raw": "| `DESCRIBE` of a navigationlist item (`item List`), a fragment widget (`container List`), or a workflow `user task Value` / `jump to Value` emits the name BARE and the output fails `mxcli check` (`mismatched input … expecting IDENTIFIER`) — same root cause as #619 but four further emitter positions the widget-name slice didn't reach | These four output sites interpolated the name directly instead of via `mdlIdent`. Unlike the strict-`IDENTIFIER` cases, the receiving grammar rules already accept `(IDENTIFIER \\| QUOTED_IDENTIFIER)` (`widgetV3` ITEM, `widgetV3`, `workflowUserTaskStmt`, `workflowJumpToStmt`), so **no grammar change is needed** — wrapping the emitted name is sufficient | `mdl/executor/cmd_pages_describe_output.go` (`item %s`, ~line 633), `mdl/executor/cmd_fragments.go` (`outputASTWidgetMDL`, ~line 169), `mdl/executor/cmd_workflows.go` (`jump to %s` ~line 321 + `formatUserTask` ~line 418) | Wrap each name in `executor.mdlIdent(...)`. Regression tests in `mdl/executor/issue619_emitter_quoting_test.go`; roundtrip fixture `mdl-examples/bug-tests/619-quoted-reserved-emitter-names.mdl`. The *other* class of #619 gaps (page-domain `microflowArgV3` show_page/microflow arg param names, `dataSourceExprV3` SELECTION ref, `sortSpec` list-sort attr) also needed a grammar widen — now done: each rule widened to `(IDENTIFIER \\| QUOTED_IDENTIFIER)` (`make grammar`), the visitor unquotes (`buildMicroflowArgV3`, `buildDataSourceV3` SELECTION, both `sortSpec` consumers), and the emitters quote via `mdlIdent` (`extractMicroflowParameters`/`extractNanoflowParameters`/`extractPageParameters`, `DataSource: selection`, list `sort(...)`, plus the fragment `selection` builder). Tests: `mdl/visitor/visitor_page_quoted_test.go` (`TestQuotedReservedSelectionAndArgNames`), `mdl/visitor/visitor_microflow_sort_quoted_test.go`, `cmd_microflows_format_listop_test.go`; fixture `mdl-examples/bug-tests/619-quoted-reserved-grammar-positions.mdl`. NOTE a reserved attribute like `Date` in a list `sort(...)` was *already* emitted bare and broken before this — the fix quotes it. Issue #619 |", "refs": ["#619"]} -{"area": "mdl/executor", "symptom": "`DESCRIBE PAGE` / `DESCRIBE STYLING` silently drops a **compound** (nested) design property — e.g. Atlas `Spacing` → margin-top/bottom — even though it was written to BSON correctly and shows in Studio Pro; only flat toggle/option props survive the read-back", "cause": "The describe-side parser `extractDesignProperties` had no `Forms$CompoundDesignPropertyValue` case (dropped it entirely), and the emitters had no `compound` branch — the write half of #668 was done but the read/roundtrip half wasn't", "file": "`mdl/executor/cmd_pages_describe_parse.go` (`extractDesignProperties` → `parseDesignProperty`) + `cmd_pages_describe_output.go` (`formatDesignPropertiesMDL` → `joinDesignPropertyEntries`) + `cmd_styling.go` (DESCRIBE STYLING emitter)", "insight": "Parse `Forms$CompoundDesignPropertyValue` by recursing over its `Properties` list (each child is again a `Forms$DesignPropertyValue`) into `rawDesignProp.Nested`; emit recursively as `'Key': ['sub': 'v', …]`. Share one formatter (`joinDesignPropertyEntries`) across both describe paths so toggle/option/compound render identically. Verify with a write→`describe`→re-`check` roundtrip (the describe output must re-parse). When a feature writes a construct, always confirm the **describe read-back** too — write-only completeness is the recurring half-shell trap. Issue #668", "refs": ["#668"]} -{"area": "mdl/executor", "symptom": "`check --references` on a view entity reports `could not parse select clause from OQL query` for a valid OQL query (any `select … from …`), AND MDL031 OQL type-mismatch checks never fire (misses real errors)", "cause": "`extractSelectClause` searched an **uppercased** query (`upperOql`) for the **lowercase** needle `\"select\"`, and compared `strings.ToUpper(oql[i:i+4])` against lowercase `\"from\"`/`\"union\"` — case mismatches that never match, so it returned `\"\"` for *every* query. That single `\"\"` both triggers the false-positive warning (`inferOQLTypes`) and short-circuits `ValidateOQLTypes` (early `return` on empty select clause) so no type checking runs", "file": "`mdl/executor/oql_type_inference.go` → `extractSelectClause`", "insight": "Compare case-consistently: `strings.Index(upperOql, \"SELECT\")` and slice keyword comparisons from `upperOql` (`word := upperOql[i:i+4]; word == \"FROM\"`). The static inferrer stays conservative (division / attribute refs → `TypeUnknown` → skipped), so re-enabling it doesn't over-fire on case/division aggregates. Tests: `TestExtractSelectClause`, `TestValidateOQLTypesNoFalsePositive` in `oql_type_inference_test.go`. Bug 9b", "rules": ["MDL031"]} -{"area": "mdl/executor", "symptom": "`mxcli widget docs` omits all but the first widget of a bundled multi-widget `.mpk` (e.g. Charts.mpk emits only `areachart.md`; ColumnChart/BarChart/PieChart/LineChart/BubbleChart missing)", "cause": "Same class as #679 in an unfixed code path: `RegenerateWidgetDocs` used `mpk.ParseMPK` (returns `WidgetFiles[0]` only), while the def-generation loop (`RefreshWidgetDefinitions`) already used `ParseMPKAll`. Charts.mpk bundles 10 widgetFiles", "file": "`mdl/executor/widget_defs.go` → `RegenerateWidgetDocs`", "insight": "Swap the per-`.mpk` `ParseMPK` for `ParseMPKAll` and loop over every returned `mpkDef` (mirror the def-gen loop). Test `TestRegenerateWidgetDocsMultiWidgetMPK` (uses the `testdata/expr-checker/widgets/Charts.mpk` fixture). Bug 9a", "refs": ["#679"]} -{"area": "mdl/executor", "symptom": "`mxcli check --references` flags `attribute 'Type' is a reserved word (CE7247) [MDL021]` even though the attribute is **quoted** (`\"Type\": String`), and the skills say \"always quote to avoid reserved-word conflicts\" — tester assumed quoting should exempt it", "cause": "NOT a bug — the check is correct. Quoting only escapes **MDL parser** keywords (`unquoteString` strips the quotes and the *bare* name is validated). `Type`, `ID`, `GUID`, `CurrentUser`, and the audit names `CreatedDate`/`ChangedDate`/`Owner`/`ChangedBy` are reserved by the Mendix **platform**, so they fail regardless of quoting. The gap was documentation: several skills claimed quoting is \"always safe\" without the platform-name carve-out", "file": "`mdl/executor/cmd_enumerations.go` (`mendixReservedWords`, `mendixSystemAttributeNames`) — no code change needed; docs only", "insight": "Add the carve-out to the \"always quote\" guidance (`check-syntax.md`, `generate-domain-model.md`, `demo-data.md`, docs-site `lexical-structure.md`) and a CLAUDE.md note: quoting is *parser*-safe, not *platform*-safe. Rename `Type`→`ResourceType`; use `AutoCreatedDate`/… pseudo-types for audit fields. Adjacent Mendix rule documented alongside: the after-startup microflow must return `Boolean` (CE0142) — a void seed microflow fails the build", "ce": ["CE0142", "CE7247"], "rules": ["MDL021"]} -{"area": "mdl/executor", "symptom": "`linkbutton` (a documented CREATE PAGE / ALTER PAGE INSERT widget) fails `exec` with \"unsupported widget type: linkbutton — refresh widget definitions\", even though `actionbutton` works in the same spot", "cause": "`linkbutton` had a grammar token, a `pages.LinkButton` stub, and docs, but **no builder and no serializer** — `buildWidgetV3`'s switch only had `button`/`actionbutton`, so it fell to `default`. Trap: the `Forms$LinkButton` metamodel type requires an `address` (a legacy static hyperlink); the documented `linkbutton (caption, action)` is really a `Forms$ActionButton` with **RenderType \"Link\"** (the toolbox \"link button\"). The `RenderMode` field already existed on `pages.ActionButton` but the serializer hardcoded `RenderType: \"Button\"`", "file": "`mdl/executor/cmd_pages_builder_v3.go` (switch) + `mdl/executor/cmd_pages_builder_v3_widgets.go` (`buildButtonV3`) + `sdk/mpr/writer_widgets_display.go` (`serializeActionButton`) + `mdl/backend/modelsdk/widget_write.go` (ActionButton case) + `mdl/executor/cmd_pages_describe_parse.go`/`cmd_pages_describe_output.go`", "insight": "Route `linkbutton` → `buildButtonV3` with `RenderMode = Link`; serialize `RenderType` from `ab.RenderMode` (default \"Button\") on both engines; DESCRIBE reads `RenderType` back and emits the `linkbutton` keyword when it is \"Link\". Reuses the proven `Forms$ActionButton` BSON (only the enum differs), so no CE0463 risk. Tests: `mdl/executor/cmd_pages_linkbutton_test.go`, `sdk/mpr/writer_widgets_linkbutton_test.go`; example in `mdl-examples/doctype-tests/03-page-examples.mdl`", "ce": ["CE0463"]} -{"area": "mdl/executor", "symptom": "In an interactive REPL running scripts back-to-back, a script that completes fine (e.g. `11-navigation-examples.mdl`, which contains `REFRESH CATALOG FULL`) leaves the session **silently disconnected** — the *next* `execute script` (or any statement) fails with `not connected to a project`. Only reproduces when a `.mxcli/catalog.db` already exists on disk (from a prior session/script) AND the project has since been written to; a from-scratch piped run never hits it", "cause": "`REFRESH CATALOG FULL` finds the on-disk cache stale (`Cache invalid: project file modified`) and calls `reconnect(ctx)`, which swaps `e.backend` for a fresh connection and syncs it back. When that reconnect fires **inside `execute script`**, the *outer* script statement's `ExecContext` was snapshotted before the reconnect, so it still holds the pre-reconnect (now-closed) backend. `executeInner`'s `syncBack` after the script then clobbers `e.backend` with that stale, closed connection → `IsConnected()` false → next statement reports not-connected. Any handler that runs nested statements via `ExecuteFn`/`ExecuteProgramFn` (EXECUTE SCRIPT, SQL connector-gen) is exposed", "file": "`mdl/executor/executor_dispatch.go` → `executeInner`/`syncBack`", "insight": "Before `syncBack`, snapshot `e.backend` prior to dispatch; if the handler didn't change its own `ectx.Backend` (`ectx.Backend == before`) but a nested call swapped `e.backend` (`e.backend != before`), adopt the live connection and its project-scoped caches (`Backend`, `MprPath`, `Cache`, `Catalog`) into `ectx` so `syncBack` preserves the reconnect instead of reverting it. **Diagnosis pattern**: \"works in a from-scratch/piped run, disconnects only in a long interactive session\" + the only state that nils the modelsdk reader is `Disconnect()` → suspect a stale outer `ExecContext` clobbering executor-global state changed by a *nested* statement; the trigger for the reconnect is a pre-existing `.mxcli/catalog.db` + a modified project. Regression: `TestReconnectInsideScript_KeepsConnection` (`reconnect_in_script_test.go`, integration-tagged)"} -{"area": "mdl/executor", "symptom": "An unrecognized property on a **built-in** (non-pluggable) widget — a typo like `Contnet`, or a genuinely unsupported key — passes `mxcli check`+`exec` with no error and is silently dropped on write (pluggable widgets already got MDL-WIDGET01)", "cause": "`validateStaticWidget` had no unknown-property check; core widgets have no single property registry — builders read keys imperatively, and `describe` even emits keys (`WidthUnit`) the native builder doesn't consume — so a hard reject would false-positive on valid MDL and break the describe→create roundtrip", "file": "`mdl/executor/validate_widgets.go` (`staticWidgetKnownProps`, `validateStaticWidgetUnknownProps`, wired in `validateWidgetTree` gated on `lookupWidgetDef==nil`)", "insight": "Add **MDL-WIDGET07** as a **WARNING** (never an error — the core-widget vocabulary can't be proven complete): flag any `Properties` key not in `staticWidgetKnownProps` (the union of grammar keyword props + builder-consumed keys + the full `describe`-emit vocabulary, harvested by grep), with a `nearestKey` \"did you mean\" hint. Runs only for non-pluggable widgets. Guard the allow-list against describe-vocabulary drift with `TestStaticWidgetKnownPropsCoverDescribe`, and sweep `mdl-examples/**` for zero false positives after any change. Bug-test `mdl-examples/bug-tests/widget-unknown-property.mdl`."} -{"area": "mdl/executor", "symptom": "Pluggable-widget datasource `sort by desc` (DataGrid2, Gallery; quoted or unquoted attr) round-trips as `asc` — `describe page` shows the direction flipped, runtime renders oldest-first, no error (silent wrong-order). Reproduces on the **default (modelsdk)** engine", "cause": "A Pages/`Forms$GridSortItem` stores its direction under the BSON key **`SortDirection`** (authoritative: the reflection-generated codec type `modelsdk/gen/pages` GridSortItem writes/reads `SortDirection`). The default engine wrote it correctly, but the DESCRIBE readers looked up the wrong key `SortOrder` (that key is only correct for `Microflows$SortItem` / `DocumentTemplates$GridSortItem`) → always fell back to `asc`. The legacy `sdk/mpr` writer *also* emitted the wrong `SortOrder` key, so under `--engine legacy` Studio Pro ignored it and reverted to ascending at runtime too", "file": "`mdl/executor/cmd_pages_describe_pluggable.go` (`gridSortDirection` helper + 3 call sites) & `cmd_pages_describe_parse.go` (1 call site); writer `sdk/mpr/writer_widgets.go` (`SerializeCustomWidgetDataSource`)", "insight": "Add `gridSortDirection(sortItem)` reading `SortDirection` with a `SortOrder` fallback (keeps pre-fix files readable); route all four grid-sort readers through it. Fix the legacy writer to emit `SortDirection`. When a sort/direction field seems misnamed, check the element's gen type in `modelsdk/gen/*/types.go` — different metamodel types genuinely use different keys (`SortDirection` for Forms/Pages grids, `SortOrder` for microflow/document-template sorts). Tests: `mdl/executor/cmd_pages_describe_sortdir_test.go`; bug-test `mdl-examples/bug-tests/bug8-datagrid-gallery-sort-desc.mdl`. Bug 8"} -{"area": "mdl/executor", "symptom": "A `use fragment` / `use building block` (or a content `slot`) nested **inside a container/layout/dataview** — not at the page-body top level — fails `exec` with `unsupported widget type: USE_FRAGMENT` (or `USE_BUILDING_BLOCK`). The same ref at the top level of the page body works. So a reusable card/panel can't be placed inside a layout column — the natural usage", "cause": "`expandFragments` (the sentinel-expansion pass) only ran on the **top-level** widget list (the two call sites in `execCreatePage`/`execCreateSnippet`); the layout/container builders build children via `buildWidgetV3` directly, which has no case for the `USE_FRAGMENT`/`USE_BUILDING_BLOCK`/`SLOT` sentinel types → falls to `default` \"unsupported widget type\"", "file": "`mdl/executor/cmd_pages_builder_v3.go` (`expandFragments`)", "insight": "Make `expandFragments` **recurse into each widget's children** after expanding the top sentinel: `for _, e := range expanded { if len(e.Children) > 0 { e.Children, _ = pb.expandFragments(e.Children) }; result = append(result, e) }`. The tree is fully expanded to concrete widgets *before* `buildWidgetV3` runs, so no builder needs a sentinel case. Expansion is idempotent on concrete widgets, so the extra traversal of an already-expanded slot payload is harmless. Verified: nested cards inside a layoutgrid column exec + `mx check` = 0 on 11.12.1. Test `TestExpandFragments_NestedInsideContainer`; bug-test `mdl-examples/bug-tests/nested-fragment-expansion.mdl`. **Diagnosis pattern**: \"works at top level, `unsupported widget type` when nested\" = an AST pre-pass (expansion/normalization) that only walks the root list; make it recurse into `.Children`"} -{"area": "mdl/executor", "symptom": "An `autonumber` attribute with **no seed** passes `mxcli check` but fails the build with **CE7247 \"Value cannot be empty\"** (`alter entity … add attribute X: autonumber` or in a `create`). Docs showed seedless `autonumber`", "cause": "`ValidateEntity` had no autonumber-seed rule; the writer emits no `AttributeValue` when `!attr.HasDefault`, so Studio Pro has no start value. Sudoku findings #6", "file": "`mdl/executor/cmd_enumerations.go` (`ValidateEntity`)", "insight": "Add **MDL023** (error): `attr.Type.Kind == ast.TypeAutoNumber && !attr.HasDefault` → \"autonumber requires a seed (`default N`)\". Also fixed the skill docs (`mdl-entities.md`, `generate-domain-model.md`) to show `autonumber default 1`. Test `TestValidateEntityAutonumberNeedsSeed`; negative bug-test `f6-autonumber-seed.fail.mdl`", "refs": ["#6"], "ce": ["CE7247"], "rules": ["MDL023"]} -{"area": "mdl/executor", "symptom": "An AutoX audit pseudo-type declared under a non-matching name — `StartedAt: autocreateddate` — silently becomes the fixed system member `CreatedDate` (declared name discarded), and binding that member in a widget then fails the build with **CE1613 \"attribute … no longer exists\"** (it's a system member, not a bindable attribute)", "cause": "The write path discards the identifier for AutoX types and `ValidateEntity` skipped them with no name check, so the rename + unbindable-member trap was silent. Sudoku findings #7", "file": "`mdl/executor/cmd_enumerations.go` (`ValidateEntity`, `autoMemberNames`)", "insight": "Add **MDL022** (warning): when an AutoX attr's name (case-insensitive) ≠ its canonical member (`owner`/`ChangedBy`/`CreatedDate`/`ChangedDate`), warn that the name is discarded and the member isn't widget-bindable — use a plain attribute you set yourself if a widget must show it. Test `TestValidateEntityAutoMemberRename`", "refs": ["#7"], "ce": ["CE1613"], "rules": ["MDL022"]} -{"area": "mdl/executor", "symptom": "A **DataGrid2 column bound to an associated attribute** (`column c (attribute: Order_Customer/Name)`) passes `mxcli check` but fails MxBuild **CE1613** \"The selected attribute 'Module.Entity.Order_Customer/Name' no longer exists.\" — an own-entity `attribute: Name` works. (Feature gap: no way to show an associated attribute in a column.)", "cause": "The grammar `attributePathV3` already accepts a bare `Assoc/Attr` path (module-qualified `M.Assoc/Attr` does not — bare only), but the reader flattened it (`resolveAttributePath` just prefixes the entity, leaving the `/` embedded → `Module.Entity.Assoc/Attr`) and both column serializers hardcoded `EntityRef: nil`. So the column stored a flat, unresolvable attribute path with no association step", "file": "`mdl/executor/cmd_pages_builder_v3.go` (`resolveAssociationAttributePath`, extracted from `resolveTemplateAssociationPath`) + `mdl/executor/widget_engine.go` (full-page column `attribute` case) + `cmd_pages_builder_v3_widgets.go` (`buildColumnSpecFromAST`, ALTER) + `mdl/backend/mutation.go` (`ObjectListItemProperty`/`DataGridColumnSpec` gain `AttributeRefSteps`) + `mdl/backend/widgetobj/builder.go` (`setAttributeRefField`+`attributeEntityRefBSON`) + `datagrid_column.go` (`buildColumnAttributeProperty`) + `mdl/executor/cmd_pages_describe_pluggable.go` (`columnAttributeFromRef`)", "insight": "Reuse the DynamicText contentparam machinery: resolve the `/`-path to a final attribute QN + `[]pages.AttributeRefStep` (hop → destination entity via `associationEndpoints`), carry the steps on the column spec, and emit `AttributeRef.EntityRef = IndirectEntityRef` of `EntityRefStep{Association, DestinationEntity}` (raw-BSON `attributeEntityRefBSON`, mirroring the codec-form `attributeRefWithStepsToGen`). DESCRIBE reconstructs the **short** `Assoc/Attr` (short association names — `attributePathV3` rejects module-qualified associations). mxbuild-validated (`mxcli docker check --no-update-widgets` = 0 errors) + describe round-trip. Tests: `mdl/backend/widgetobj/widget_builder_attribute_ref_test.go`, `mdl/executor/cmd_pages_describe_column_assoc_test.go`; bug-test `mdl-examples/bug-tests/datagrid2-associated-attribute-column.mdl`. Bug 7", "ce": ["CE1613"]} -{"area": "mdl/executor", "symptom": "A DataGrid2 column's `DynamicCellClass: ''` (per-cell dynamic CSS class) parses, passes `check` + `mxbuild`, but is **silently dropped** — `describe` shows no `DynamicCellClass`, the `columnClass` slot is written as an **empty** expression, runtime cell is unstyled. Both engines", "cause": "The DataGrid `columns` object-list mapping (`itemPropertyAliases`) had aliases for `header←Caption`, `dynamicText←Content`, `width←ColumnWidth` but **none** for `columnClass`. `buildObjectListItem` looks up the schema key + its MDL aliases in the AST property bag (case-insensitive via `lookupProperty`), so `DynamicCellClass` never matched → the property fell through to the template's empty default. Cached `.mxcli/widgets/datagrid.def.json` files also had to regenerate to carry the new alias", "file": "`mdl/executor/widget_defs.go` (`itemPropertyAliases`, `columns.columnClass`) + `mdl/executor/widget_engine.go` (`WidgetDefGeneratorVersion` bump)", "insight": "Add `\"columnClass\": {\"DynamicCellClass\"}` to the datagrid `columns` aliases (schema property is `type: \"expression\"` → `operationForType` → written via the expression branch). **Bump `WidgetDefGeneratorVersion`** (5→6) so existing projects' cached def.json auto-regenerate via `RefreshStaleWidgetDefinitions` and pick up the alias — a code-only alias add is invisible until the stamped def is refreshed. DESCRIBE already reads `columnClass`→`DynamicCellClass` (`cmd_pages_describe_pluggable.go`). Test: `TestObjectListItemAliases`; bug-test `mdl-examples/bug-tests/bug10-dynamic-css-classes.mdl`. Bug 10a"} -{"area": "mdl/executor", "symptom": "A `dynamictext` contentparam over an association fails mxbuild (\"No value specified\") — **re-reported as still-broken after the modelsdk fix**. Investigation: the contentparam is actually **already correct on the default (modelsdk) engine** in every *valid* container (datagrid custom-content column, `listview + database`, `dataview` over a page parameter — all mx check 0 errors). The only failing repro is a `dataview (datasource: database …)`, which is an **invalid Mendix construct** — a data view shows one object, so Mendix offers only Context/Microflow/Nanoflow/Listen sources, never Database (that's for list widgets). mxcli wrongly accepted it: modelsdk errored \"not yet supported — rerun with legacy\", and legacy silently wrote a `Forms$DataViewSource` that mxbuild rejects with **CE7007** \"Selected value is not valid for entity\"", "cause": "mxcli had no check for a DataView database source (only the association case, MDL-WIDGET08). So the invalid construct routed users to the deprecated legacy engine, which produced a broken page. **Do not \"fix\" legacy** (`LEGACY_ENGINE_KNOWN_ISSUES.md`: legacy is being removed, not patched) and **do not implement dataview+database in modelsdk** (Mendix doesn't allow it) — reject it at check, sibling to MDL-WIDGET08", "file": "`mdl/executor/validate_widgets.go` (`validateStaticWidget`, MDL-WIDGET09) + `mdl/executor/cmd_pages_builder_v3_widgets.go` (`buildDataViewV3` exec refusal)", "insight": "Add **MDL-WIDGET09**: `dataview` + `database` source → error steering to a microflow/nanoflow source (or page parameter), or a list widget. Refuse it in `buildDataViewV3` too (both engines) so a bare `exec` can't create a broken page. When a construct fails only on legacy, first check whether it's valid on modelsdk in a *valid* container — the fix is usually a check-time rejection of the invalid form, not a legacy patch. Tests: `TestValidateStaticWidget_DataViewDatabaseSource`; bug-test `mdl-examples/bug-tests/dataview-database-source-rejected.fail.mdl` (negative). Bug 3 (re-report)", "ce": ["CE7007"]} -{"area": "mdl/executor", "symptom": "A `dynamictext` contentparam (or DataGrid2 column) navigating an association still fails mxbuild **CE0402 \"No value specified\"** — the *actually unsolved* Bug 3 case. Reproduces only when the widget's entity context is a **specialization** of the entity that declares the association: a grid over `SpecialExpense extends Expense` with `contentparams: [{1} = Expense_Employee/Name]` (the `Expense_Employee` association is on the base `Expense`). An exact-endpoint context (grid over `Expense`) works. `describe` shows `[{1} = ]`; the binding is dropped. **Very common in real apps** (entities inherit from a base, associations on the base) — which is why the earlier fixes looked incomplete", "cause": "`resolveAssociationAttributePath` → `associationDestination` matched the context against the association's FROM/TO **by exact string equality**. A subclass context equalled neither endpoint, so it returned `ok=false` and the caller fell back to the flat, unresolvable `Assoc/Attr` path → no valid AttributeRef → CE0402. Affects both engines (shared builder) and both the contentparam and Bug 7 column-attribute paths", "file": "`mdl/executor/cmd_pages_builder_v3.go` (`associationDestination`, new `entityIsOrDescendsFrom` + `entityGeneralizations`)", "insight": "Match the endpoint the context **is or descends from**: walk the generalization chain (`Entity.GeneralizationRef`, qualified parent name) so an association declared on a base entity resolves from a subclass context. Diagnose association-binding drops by checking whether the widget's entity context exactly equals the association endpoint — inheritance is the usual culprit. mxbuild-verified 0 errors across grid-over-base, listview, and grid-over-subclass. Tests: `TestResolveAssociationAttributePath_InheritedContext`; bug-test `mdl-examples/bug-tests/bug3-contentparam-inherited-association.mdl`. Bug 3 (inheritance)", "ce": ["CE0402"]} -{"area": "mdl/executor", "symptom": "`DESCRIBE` of a generic pluggable widget (chart) omits its object-list child blocks — `series`/`line`/`scalecolor` items are dropped, so describe→exec loses the chart's data. (Pre-existing; SERIES always had it.) DataGrid2/Gallery were unaffected — they have specialized column output", "cause": "The generic `pluggablewidget` DESCRIBE branch only emitted scalar `ExplicitProperties`; nothing walked the widget's `WidgetObject` lists. (The DataGrid2 column reconstruction, `extractDataGrid2Columns`, was the only object-list reader and is hard-coded to `columns`.)", "file": "`mdl/executor/cmd_pages_describe_objectlist.go` (`extractObjectLists`, `buildObjectListNestedKeyMap`, `extractObjectListItem`, `objectListMDLKey`) + wired into `cmd_pages_describe_parse.go` (generic-pluggable branch) + `cmd_pages_describe_output.go` (emit child blocks)", "insight": "Generalize the column-reconstruction pattern: `buildObjectListNestedKeyMap` (like `buildColumnPropertyKeyMap` but for any list key) resolves the item's `TypePointer→sub-key` map from `Type.ObjectType.PropertyTypes[listKey].ValueType.ObjectType.PropertyTypes`; per item, read datasource / AttributeRef / Expression / TextTemplate / PrimitiveValue and map the schema key to PascalCase MDL (`staticXAttribute`→`StaticXAttribute`). Keyword via `deriveObjectListKeyword` (lowercased). Only runs for the generic branch (`!isKnownCustomWidgetType`), so DataGrid2/Gallery keep their specialized output — no double-emission. Verified: describe→exec→describe is byte-identical on bar/line/heatmap. Tests: `TestExtractObjectListItem_ChartSeries`, `TestObjectListMDLKey`."} -{"area": "mdl/executor", "symptom": "A HeatMap `scalecolor` entry's `ColorValue: '#rrggbb'` parses and passes `check` but the colour is **silently dropped on write** — describe shows only `ValuePercentage`, and the runtime scale has no colour. `valuePercentage` persists fine", "cause": "The schema property is spelled **`colour`** (British), and `scaleColors` had no `ColorValue` MDL alias, so the engine looked up `colour`, didn't find `ColorValue`, and wrote the template default (empty). Identical mechanism to Bug 10a (`columnClass`←`DynamicCellClass`) — a missing `itemPropertyAliases` entry", "file": "`mdl/executor/widget_defs.go` (`itemPropertyAliases`, `heatmap.scaleColors.colour`) + `mdl/executor/widget_engine.go` (`WidgetDefGeneratorVersion` bump)", "insight": "Add `\"colour\": {\"ColorValue\"}` to the HeatMap `scaleColors` aliases and **bump `WidgetDefGeneratorVersion`** so cached `.mxcli/widgets/heatmap.def.json` regenerate. DESCRIBE reconstructs it as `Colour:` (the PascalCase schema key — round-trips case-insensitively). Verified: `#rrggbb` now in BSON; describe→exec byte-stable. Test: `TestObjectListItemAliases_HeatMapColour`. Bug 10a class"} -{"area": "mdl/executor", "symptom": "A **PieChart/HeatMap** (charts that bind data at the WIDGET level, no series object-list) fails MxBuild **after `mx update-widgets`** with **CE0642** \"Value attribute is required\" and (PieChart) **CE4899** \"Series name is required\" — even though `mxcli check` + `exec` are clean. The datasource persists; the value attribute + series name don't. Only surfaces once CE0463 version-drift is cleared by update-widgets", "cause": "These widgets expose several attribute-typed top-level properties (`seriesValueAttribute`, `seriesSortAttribute`) plus a required `seriesName` texttemplate. The engine's `resolveMapping` `\"Attribute\"` case read a single generic `w.GetAttribute()` — ambiguous across multiple attribute props and blind to the friendly MDL names — so `ValueAttribute:` never reached `seriesValueAttribute`; and `GenerateDefJSON` **skips all top-level texttemplate props**, so `seriesName` had no mapping. Item 1b", "file": "`mdl/executor/widget_defs.go` (`propertyAliases`, `GenerateDefJSON` attribute-alias + gated texttemplate case) + `mdl/executor/widget_engine.go` (`PropertyMapping.MdlAliases`, `namedPropValue`, `resolveMapping` `\"Attribute\"`/`\"TextTemplate\"`, version bump)", "insight": "Add `PropertyMapping.MdlAliases` (top-level analog of `ItemPropertyMapping.MdlAliases`) + a `propertyAliases` map (piechart/heatmap `seriesValueAttribute`←`ValueAttribute`, piechart `seriesName`←`SeriesName`). `resolveMapping` `\"Attribute\"` now reads via `namedPropValue` (property key + aliases) and resolves against the widget datasource entity (the DataSource mapping is ordered first), falling back to `w.GetAttribute()` for single-attribute widgets. Emit a top-level texttemplate mapping **only** when an alias is registered (keeps the broad skip). Bump `WidgetDefGeneratorVersion`; the example must supply the required `SeriesName:`. mxbuild-verified: whole chart file = 0 errors after update-widgets. **Diagnose \"required property\" CE0642/CE4899 by running `mx update-widgets` first (clears CE0463), then check which schema key is missing from the BSON.** Tests: `TestResolveMapping_NamedAttribute`, `TestGenerateDefJSON_PieChartNamedProperties`. Widget-level DESCRIBE of `seriesName`/datasource is a separate gap. Item 1b", "ce": ["CE0463", "CE0642", "CE4899"]} -{"area": "mdl/executor", "symptom": "A view entity whose attribute is named after a Mendix OQL keyword — most often a date-part word like `Quarter`/`Month`/`Year` — passes `mxcli check`/`exec` but fails **MxBuild CE0174** \"The 'Quarter' part is incomplete or incorrect. You could use here: … OPEN_QUOTE, or IDENTIFIER\". A `Region`/`Period` column is fine", "cause": "OQL reads the bare word as the keyword, not the attribute. **This row previously said mxcli cannot escape it and that `s.\"Quarter\"` is a parse error — both are wrong**, and the error text quoted in the row disproves the first (it lists `OPEN_QUOTE` among what is valid there)", "file": "`mdl/executor/oql_type_inference.go` (`ValidateOQLSyntax`, `oqlReservedWords`, **MDL032**, **MDL072**); `mdl/executor/validate_oql_reserved_names.go` (**MDL071**)", "insight": "**Quote it in a SOURCE position** — `select s.\"Quarter\" …`, `from Module.\"Year\" as s` — measured at 0 errors on 11.13.0, with the quotes passed through verbatim (`RawQuery` via `extractOriginalText`). The **alias** is the one position OQL will not take a quote in, for any name at all, so a view entity's own attribute — whose name IS its alias — has to be renamed; that is the only case that does. Read the two CE0174 texts side by side before concluding anything about quoting: the source position lists `ASTERISK, AT_SIGN, OPEN_QUOTE, or IDENTIFIER` and the alias position lists only `IDENTIFIER`. MDL071 warns at CREATE (where a rename is still cheap), MDL032 inside a view's OQL, MDL072 on the quoted-alias spelling.", "ce": ["CE0174"], "rules": ["MDL032", "MDL071", "MDL072"]} -{"area": "mdl/executor", "symptom": "A view entity with a **derived string column** — `cast(x as string)`, a string-returning `CASE`, or a string expression — passes `mxcli check`/`exec` but fails **MxBuild CE6770** \"View Entity is out of sync with the OQL Query\" whenever the declared attribute length is anything other than **200** (e.g. `string(30)`, `string(50)`, unlimited). The OQL itself runs fine, so it looks like a serialization gap but is a **platform rule**: Mendix normalizes a derived string column to the default length String(200); a plain pass-through column (`c.Name as Name`) instead inherits its source length", "cause": "Two parts. (1) The rule: only *derived* string columns are forced to 200 — a bare attribute ref infers `TypeUnknown` and is skipped, so any concretely-typed String the static inferrer sees is derived → 200. (2) The checker was **dead**: `inferTypeStatic`/`inferTypeFromExpression`/`inferCaseType` uppercased the expr (`upper := ToUpper(...)`) then compared against **lowercase** literals (`HasPrefix(upper, \"cast(\")`, `\"count(\"`, `\"case\"`, `== \"true\"` …), so every prefix check failed and only `DATEPART(` (uppercase literal) ever matched — CAST/CASE/COUNT/SUM/AVG/MIN/MAX/LENGTH inference all returned Unknown. Sibling of Bug 9b's `extractSelectClause` case bug", "file": "`mdl/executor/oql_type_inference.go` (`inferTypeStatic` CAST/CASE + case-fixed prefixes, `castTargetType`, `derivedStringLength=200`, `ValidateOQLTypes` string-length normalize, `typesStrictlyCompatible` length compare, `inferCaseType`, `inferTypeFromExpression`) — **MDL031**", "insight": "Infer `cast(expr AS string)` and string CASE as `String(200)`; in `ValidateOQLTypes` normalize any inferred `TypeString` length to 200 (derived); `typesStrictlyCompatible` compares String **length** (not just kind), so `string(30)` vs `String(200)` is flagged with `Fix: change to 'X: String(200)'`. **Fix the case-comparison bug** (lowercase→UPPERCASE literals) so the checker actually runs. **Guard against false positives**: `SUM(` over an un-inferable inner (a bare attribute ref → Unknown) returns **Unknown**, not a guessed Decimal — otherwise `sum(s.Units)` declared `integer` false-fires (caught on `34-chart-widget-examples.mdl`). mxbuild-confirmed on 11.6.6: `string(30)`→CE6770, `string(200)`→0 errors. Tests: `TestValidateOQLTypesDerivedString`, `TestValidateOQLTypesNoFalsePositive`; bug-test `mdl-examples/bug-tests/view-entity-derived-string-length.mdl`", "ce": ["CE6770"], "rules": ["MDL031"]} -{"area": "mdl/executor", "symptom": "A view entity with a **pass-through string column** — a bare source-attribute reference like `select c.Name as CategoryName` — declared with a length **different from the source attribute** (most often an unbounded `string` against a `string(100)` source) passes `mxcli check` but fails **MxBuild CE6770** \"View Entity out of sync\". This is the counterpart of the *derived*-column rule (which forces 200): a pass-through column inherits the source length **exactly**", "cause": "The references-mode validator (`validateViewEntityTypes`) compared with `typesCompatible`, whose String rule only guards **truncation** (`declared.Length >= inferred.Length`), so an unbounded `string` (Length 0) or a wider `string(200)` against a `string(100)` source slipped through. Only *derived* columns (via `ValidateOQLTypes`/`typesStrictlyCompatible`, syntax mode) were length-exact", "file": "`mdl/executor/oql_type_inference.go` (`passthroughStringLengthMismatch`, called in `validateViewEntityTypes` before the generic `typesCompatible`)", "insight": "For a pass-through column (`col.SourceAttr != \"\"`, set only for a bare `alias.attr` ref — aggregates use a throwaway col so it stays empty) require **exact** String length match; emit the source entity/attr + inherited length in the message. **References-mode only** (needs the domain model to resolve the source attribute's length), so the repro is not a `.fail.mdl` (the syntax-only `make check-mdl` harness can't see it). Test `TestPassthroughStringLengthMismatch`; example `mdl-examples/bug-tests/ledger-36-view-passthrough-length.mdl`; mxbuild-confirmed on 11.12.1 (`string`→CE6770, `string(100)`→clean). Ledger finding #36", "refs": ["#36"], "ce": ["CE6770"]} -{"area": "mdl/executor", "raw": "| A microflow `declare $x Module.Entity [= $obj];` (object/entity-typed local variable) passes `mxcli check` but mxbuild rejects it with **CE0053** \"Selected type is not allowed\" (+ CE0038 \"Value required\", + CE7247 on a following `set`) — a silent trap. The declare-entity attribute qualifier is a red herring: the whole construct is invalid, bare or initialized | Mendix's Create Variable activity (what `declare` maps to) only holds **primitive** types; there is no object/list Create Variable form (same restriction as MDL040 for lists). Objects must come from a parameter, a `retrieve … limit 1`, a `create` object, or a loop iterator. The check was missing; the microflow parser records a bare `Module.X` declare type as the ambiguous `TypeEnumeration` (`EnumRef`, `ExplicitEnum=false`), an explicit `Enumeration(Module.X)` sets `ExplicitEnum=true` | `mdl/executor/validate_microflow.go` (`walkBody` `*ast.DeclareStmt` case, next to the MDL040 list check) | Add **MDL043**: flag `Type.Kind == TypeEntity \\|\\| (TypeEnumeration && EnumRef != nil && !ExplicitEnum)`; error points to parameter/retrieve/create/loop and notes \"if it's an enum, write `Enumeration(...)`\". Reliable connection-free (no backend) because bare object names resolve to a distinct AST shape from explicit enums. Also fix the skills that wrongly taught `declare $x Module.Entity;` / `declare $x as …` as valid (`write-microflows.md`, `cheatsheet-variables.md`, `cheatsheet-errors.md`, `check-syntax.md`, `patterns-crud.md`, `patterns-data-processing.md`, `business-events.md`, `resolve-forward-references.md`, `migrate-k2-nintex.md`, `README.md`). Test: `TestValidateMicroflow_DeclareObjectIsRejected`; bug-test `mdl-examples/bug-tests/declare-object-variable-rejected.fail.mdl`. mxbuild-confirmed (11.12.0) |", "ce": ["CE0038", "CE0053", "CE7247"], "rules": ["MDL040", "MDL043"]} -{"area": "mdl/executor", "symptom": "`alter entity M.E add attribute X: autonumber;` (no seed) or `... add attribute Created: AutoCreatedDate;` (renamed AutoX) passes `mxcli check` \"Check passed!\" but fails the build (CE7247) / silently discards the name — while the SAME attribute in `create entity` is correctly flagged (MDL023 / MDL022)", "cause": "The per-attribute checks (MDL021/022/023) only ran on `CreateEntityStmt`; the ALTER ENTITY ADD ATTRIBUTE path had no validation at all, so an attribute added later escaped every rule", "file": "`mdl/executor/cmd_enumerations.go` (`ValidateAlterEntity`, `validateEntityAttribute`) wired from `cmd/mxcli/cmd_check.go` + `cmd/mxcli/lsp_diagnostics.go`", "insight": "Extract the CREATE loop body into `validateEntityAttribute(attr, persistent, entityName)`; add `ValidateAlterEntity(stmt)` that runs it on `AlterEntityAddAttribute`. The entity kind isn't known from ALTER, so the persistent-only MDL020 is skipped; the kind-independent MDL021/022/023 all run. Bug-test `mdl-examples/bug-tests/f6-autonumber-seed-alter.fail.mdl`. Findings #6 (alter path)", "refs": ["#6"], "ce": ["CE7247"], "rules": ["MDL020", "MDL021", "MDL022", "MDL023"]} -{"area": "mdl/executor", "symptom": "`create or modify entity M.E ( )` on an entity that already has more attributes **silently drops** every attribute not re-listed (36→2 attrs seen in practice), then widgets/microflows still bound to them fail the build with CE1613 — and the \"already exists\" error that leads users here recommends the destructive `create or modify` for a partial edit", "cause": "`create or modify` rebuilds the entity from the statement alone and REPLACEs the stored one, so any omitted attribute is deleted with no warning; the `NewAlreadyExistsMsg` hint pointed at `create or modify` without distinguishing \"replace whole\" from \"add one\"", "file": "`mdl/executor/cmd_entities.go` (`droppedEntityMembers`, the warn block before `UpdateEntity`, and the `execCreateEntity` already-exists message)", "insight": "Warn-only (non-blocking, the user asked to modify): before `UpdateEntity`, diff existing vs replacement members (`droppedEntityMembers` — named attrs case-insensitive + the four audit flags) and print what's dropped + point at `alter entity … add attribute` for incremental edits. Fix the already-exists message to recommend `alter entity` for a member change and reserve `create or modify` for a full replace. Bug-test `mdl-examples/bug-tests/f24-create-or-modify-dataloss.mdl`. Findings #24", "refs": ["#24"], "ce": ["CE1613"]} -{"area": "mdl/executor", "symptom": "Design properties are written free-form: a `ColorPicker`/`ToggleButtonGroup` value serializes as a plain option (wrong `$Type` for Studio Pro's Appearance tab), and a typo'd key/value (they're case-sensitive) passes `mxcli check`. Also `show design properties ` reports \"No design properties found for widget type container\" for a valid widget", "cause": "Root bug: `resolveDesignPropsKey` upper-cased the MDL keyword but the lookup map is **lowercase-keyed**, so `container`→`DivContainer` never resolved — leaving `resolveDesignPropertyValueType` dead code and the theme registry unused on the write/validate paths", "file": "`mdl/executor/theme_reader.go` (`resolveDesignPropsKey` case fix) + `mdl/executor/cmd_pages_builder_v3.go` (`astDesignPropToValue` takes theme props) + `mdl/executor/validate_design_properties.go` (new, MDL-WIDGET11/12) wired from `cmd/mxcli/cmd_check.go` + `cmd/mxcli/lsp_diagnostics.go` (cached `themeRegistry`)", "insight": "Fix `resolveDesignPropsKey` to lower-case the lookup. On write, resolve each flat value's type from the registry **by matching the value against the property's declared options** (see the CE6084 correction below — the control type alone does NOT decide it). On check (`-p` only, when themesource defines properties), walk page/snippet/alter-page widget trees and warn: **MDL-WIDGET11** unknown key (case-sensitivity hint / valid-key list), **MDL-WIDGET12** invalid value (lists allowed values). Warnings, not errors — a newer theme may add keys/values (forward-compat, per `page-styling-support.md:402`). Skip compound (registry doesn't model sub-props) and widgets with no type-specific metadata (pluggable). Bug-test `mdl-examples/bug-tests/typed-design-properties.mdl`", "ce": ["CE6084"]} -{"area": "mdl/executor", "symptom": "Follow-up regression from the row above: after typed design properties merged, `mx check` fails **CE6084** \"Expected design property _Flex container_ / _Column gap_ / _Align items Y_ … to be of type **Toggle button group**, but found **Custom**\" on any page using a flat `ToggleButtonGroup` value (Atlas flex/spacing/typography, e.g. `'Column gap': 'Medium'`). Broke `TestMxCheck_DoctypeScripts` on `12-styling`, `15c-fragment-bindings`, `31-pluggable-datagrid-gallery-v010` (both engines) — green on unit tests, red only in `make test-integration`", "cause": "`resolveDesignPropertyValueType` mapped `ToggleButtonGroup`→`custom` by control type. But a ToggleButtonGroup selection picks one of a **fixed option set**, so Studio Pro stores it as an **Option** — a `Custom` value type mismatches the declaration. Only a ColorPicker's **off-list** value (a free-form hex) is genuinely Custom. The value type is decided by the **value**, not the control", "file": "`mdl/executor/cmd_pages_builder_v3.go` (`resolveDesignPropertyValueType`, now takes the value and reuses `themeOptionAllowed`)", "insight": "Make it value-aware: value ∈ declared options → `option` (Dropdown, ToggleButtonGroup, predefined ColorPicker swatch alike); off-list **and** `ColorPicker` → `custom`; else `option`; no metadata → `option`. Verified: the three doctype examples pass `mx check` = 0 errors on both engines. Test `TestAstDesignPropToValue_Typed` extended with the `Column gap: Medium` + ColorPicker swatch/hex cases. **Diagnosis pattern**: a value-type/BSON-`$Type` mapping keyed on a *declared control type* is a trap — verify it against `mx check`, never assert it from the type name alone (this is exactly how the original bug slipped in). **Process lesson**: this shipped red because `make test-integration` (mx-check doctype roundtrips) was not run before merge — run it, not just unit tests, for any page/widget-serialization change", "ce": ["CE6084"]} -{"area": "mdl/executor", "symptom": "`create or modify persistent entity` (even a byte-for-byte identical re-run) NULLs every column value on the next runtime DB sync — rows survive, values gone. Reports success (`Modified entity`), `mx check` = 0 errors; the loss only surfaces when something reads the data. Diagnose via the DB-sync count in `runtime.log` (\"Executing N database synchronization command(s)\") on an unchanged model", "cause": "`execCreateEntity` minted a FRESH attribute ID for EVERY attribute each run, even unchanged ones. Mendix's DB synchronizer keys off attribute identity — a new ID reads as \"attribute departed + new attribute added\", so it drops and re-adds the column. Only the entity's own ID was preserved; ALTER ENTITY (the safe path) mutates loaded attributes in place, keeping IDs", "file": "`mdl/executor/cmd_entities.go` (`execCreateEntity`)", "insight": "On CREATE OR MODIFY of an existing entity, build a name→ID map from `existingEntity.Attributes` and reuse the existing ID for retained attributes (only new attributes get a fresh ID); `attrNameToID` propagates the reused ID into validation rules + indexes. Also steer the \"already exists in project\" hint (`validate_duplicates.go`) toward `alter entity … add attribute` for entities. Test `TestCreateOrModifyEntity_PreservesAttributeIDs`; repro `mdl-examples/bug-tests/create-or-modify-preserves-attribute-ids.mdl`. Findings #13", "refs": ["#13"]} -{"area": "mdl/executor", "symptom": "A widget EXPRESSION property (`dynamicclasses`/`visibleif`/`editableif`) that walks an association passes `mxcli check --references` but fails `mx check` with CE0117 \"Error(s) in expression.\" Easy to trip: a data binding (`contentparams`) on the SAME widget can traverse the same association legitimately", "cause": "No check inspected expression-typed widget property values for association steps (MDL-WIDGET04/07/etc. check placeholders/keys, not expression contents). Mendix client-side expressions cannot follow associations — only data bindings can", "file": "`mdl/executor/validate_widgets.go` (`validateWidgetExpressionAssociations`, called from `validateStaticWidget`)", "insight": "New MDL-WIDGET13: for `DynamicClasses`/`VisibleIf`/`EditableIf`, regex `exprAssociationStepRe` (`/Ident.Ident/`) flags a module-qualified step between slashes; a plain attribute (`$obj/Slug`) or enum literal (`Mod.Enum.Val`, no leading slash) doesn't match. Fix for the user: denormalise the attribute onto the bound entity (calculated attribute) or use a data binding. Test `TestValidateWidgetExpressionAssociations`. Findings #4", "refs": ["#4"], "ce": ["CE0117"]} -{"area": "mdl/executor", "symptom": "Checker HINTS send the author the wrong way: MDL001 (nested loop) recommends `retrieve $Match from $List where … limit 1` (a parse error — can't filter a list variable); MDL044 flags `count()` in an expression with \"Did you mean 'round()'?\" (count is an aggregate, not a typo); MDL044's hint cites `mxcli syntax expressions` (no such topic)", "cause": "Message-only defects in the linter", "file": "`mdl/executor/validate_microflow.go` (MDL001 message ~216; `checkExprFunctions` ~283; `mendixAggregateFuncs`)", "insight": "MDL001 → recommend `$Match = FIND($List, )` (in-memory O(N); also fix CLAUDE.md idiom #2); MDL044 → for `count/sum/average/minimum/maximum` emit \"aggregate activity, not an expression function — assign to a variable first: `$n = count($List);`\" instead of a did-you-mean; point the generic hint at `mxcli syntax microflow`. Findings #7/#8/#14c", "refs": ["#14", "#2", "#7", "#8"], "rules": ["MDL001", "MDL044"]} -{"area": "mdl/executor", "symptom": "A microflow-CALL output variable reused across a fallback chain (`$S = call M.Inner(...); if … then $S = call M.Inner(...) end if;`) — the natural \"try A else try B\" — fails the build CE0111 \"Duplicate variable name\"", "cause": "Each `$Var = call microflow/create/retrieve …` is a *fresh* variable creation; reusing the name (even inside an if) is a duplicate. `check --references` runs the flowBuilder validation (`validateOutputVariable`) which catches it — bare `check` (no project) does not", "file": "`mdl/executor/cmd_microflows_builder_validate.go` (`validateOutputVariable`, `validateScopedStatements`)", "insight": "Already caught by the shared create-output-var check (same fix as the retrieve/create case); locked in by `TestValidateDuplicateMicroflowCallOutputVar` (same-scope + nested-in-if). Fix for the user: one variable per call, then a plain `set` picks the winner (documented in write-microflows.md). Finding #5 (2nd trigger)", "refs": ["#5"], "ce": ["CE0111"]} -{"area": "mdl/executor", "symptom": "`call javascript action` / `call java action` with a wrong-cased or misspelled PARAMETER name passes `check --references` (the action itself resolves) but writes a dangling reference that fails the build with CE1613 \"The selected … parameter … no longer exists\". E.g. `NanoflowCommons.OpenURL(url = …)` when the parameter is `Url`", "cause": "The reference checker resolved only the action NAME (`buildJava*ActionQualifiedNames` returns names, discarding params); the call's `CallArgument` names were never compared to the action's declared parameters", "file": "`mdl/executor/validate.go` (`flowRefCollector` → `codeActionCallRef`, `validateCodeActionParams`)", "insight": "Carry the call's `argNames` on the collector; after the name-exists check, `ReadJavaScriptActionByName`/`ReadJavaActionByName` → `.Parameters[].Name`, diff case-sensitively; casing-only mismatch → did-you-mean. Skip `System.*` (runtime-provided) and degrade to no-error when the backend reports no params. References-mode only (needs `-p`). Tests `TestValidateCodeActionParams`. RSS-reader follow-up finding", "ce": ["CE1613"]} -{"area": "mdl/executor", "symptom": "`DESCRIBE PAGE` omits a textbox's `placeholder`/`onchange` even though they are written correctly (present in the .mxunit, live app works) — so a DESCRIBE round-trip is not a reliable way to confirm the write landed", "cause": "The describe read path (`parseRawWidget`) only read `LabelTemplate` + `AttributeRef` for a textbox; `PlaceholderTemplate` and `OnChangeAction` were never read back into `rawWidget`", "file": "`mdl/executor/cmd_pages_describe_parse.go` (`extractPlaceholderText`), `cmd_pages_describe.go` (`rawWidget` fields), `cmd_pages_describe_output.go` (`renderClientActionMDL`/`extractOnChangeAction` + TextBox emit)", "insight": "Add `Placeholder`/`OnChange` to `rawWidget`; read `PlaceholderTemplate` via `extractTextFromTemplate` (same as label) and `OnChangeAction` via a key-agnostic `renderClientActionMDL` (refactored out of `extractButtonAction`, since OnChangeAction is the same client-action type under a different key); emit `Placeholder:`/`OnChange:` in the TextBox case. Single describe path serves both engines (reads raw BSON via `GetRawUnit`). Test `TestParseRawWidget_TextBoxPlaceholderAndOnChange`. RSS-reader follow-up (verification note on #9)", "refs": ["#9"]} -{"area": "mdl/executor", "symptom": "`DESCRIBE PAGE` of a `dynamictext` bound to a NON-STRING attribute (Integer/DateTime/…) emits `ContentParams: [{1} = toString($currentObject/Attr)]`; re-applying that output fails the build with CE1613 \"attribute '…toString($currentObject/Attr)' no longer exists\" — the rendered expression is treated as an attribute NAME. A string binding round-trips fine (bare attribute name)", "cause": "The write side converts a non-String attribute binding to `toString($currentObject/Attr)` (`resolveTemplateAttributePathFull`), stored as a ClientTemplateParameter `Expression`; the describe reader emitted the Expression verbatim instead of reversing the transform", "file": "`mdl/executor/cmd_pages_describe_output.go` (`unwrapToStringAttrParam` in `extractClientTemplateParameters`)", "insight": "When a ContentParam Expression is exactly `toString($currentObject/)` or `toString($param/)` (the auto-generated forms), emit the bare `` / `$param.attr`; the write side re-derives the toString on the next apply, so it round-trips. Any other expression (extra text, hand-written toString) is left untouched. Tests `TestUnwrapToStringAttrParam`, `TestParseRawWidget_DynamicTextNonStringAttribute`. RSS-reader follow-up finding", "ce": ["CE1613"]} -{"area": "mdl/executor", "raw": "| A `contentparams`/`captionparams` value bound to a client EXPRESSION (`[{1} = formatDateTime($obj/LastImport, 'd MMM yyyy')]`) passes `mxcli check` but Studio Pro rejects the page with CE1613 \"attribute … no longer exists\" — the whole expression is stored as a bogus attribute name. A plain attribute path (`$obj/Attr`) or quoted literal (`'text'`) works | **mxcli** treats a template-parameter slot as a data binding: `buildClientTemplateParams` stores any unquoted value as an attribute path (`resolveTemplateAttributePathFull`), so an expression becomes a bogus attribute name. No check inspected the value for expression syntax. NOTE the original row said a template parameter *is* a data binding \"not an expression\" — that is a claim about MENDIX and it is **false**: Studio Pro's Edit Template Parameter dialog offers `Parameter type: Value | Expression`, with its own editor, variable list and wizard, and `Pages$ClientTemplateParameter` carries `Expression` beside `AttributeRef` and `SourceVariable`. The limit is mxcli's | `mdl/executor/validate_widgets.go` (`validateTemplateParamExpressions`, called from `validateStaticWidget`) | New **MDL-WIDGET14**: for each `ContentParams`/`CaptionParams` value, skip quoted string literals, then regex `templateParamExprRe` (`Ident(` function call or an arithmetic/comparison operator) flags an expression; an attribute path never matches. Fix for the user: set the parameter's type to Expression in Studio Pro, or bind an attribute path / quoted literal in MDL — do NOT send them to build a calculated attribute for something the platform already offers (the message used to, which is unnecessary modelling). Test `TestValidateTemplateParamExpressions`; bug-test `mdl-examples/bug-tests/contentparam-expression-rejected.fail.mdl`. Ledger finding #26 |", "refs": ["#26"], "ce": ["CE1613"]} -{"area": "mdl/executor", "symptom": "A microflow expression using `dateTime(2026, $Month, $Day)` (a variable/computed arg) passes `mxcli check` but fails the build with CE0117 — Mendix builds `dateTime()`/`dateTimeUTC()` from hardcoded numeric constants only", "cause": "No check inspected date-construction arguments; the function name resolves so the unknown-function check (MDL044) is silent", "file": "`mdl/executor/validate_microflow.go` (`checkDateTimeLiterals`/`exprHasNonLiteralDateTime`, MDL046)", "insight": "New **MDL046**: walk the expression for a `FunctionCallExpr` named `dateTime`/`dateTimeUTC` with any non-`LiteralExpr` argument. Fix for the user: step off a literal anchor date — `addDays(addMonths(dateTime(2026,1,1), $Month-1), $Day-1)` (addDays/addMonths take variables). Test `TestValidateMicroflow_DateTimeLiterals`; repro `mdl-examples/bug-tests/ledger-21-datetime-literals.fail.mdl`. Ledger finding #21", "refs": ["#21"], "ce": ["CE0117"], "rules": ["MDL044", "MDL046"]} -{"area": "mdl/executor", "symptom": "A retrieve constraint `where [Ledger.Transaction_Category = empty]` (an ASSOCIATION `= empty`) passes `mxcli check` but fails the build with CE0161 \"Error(s) in XPath constraint\" — XPath `= empty` tests attribute nullability, not association nullability", "cause": "No check inspected constraint strings for an association compared to `empty`; a bare attribute `= empty` IS valid, so a blanket ban would false-positive", "file": "`mdl/executor/validate_microflow.go` (`checkXPathAssociationEmpty`/`xpathAssocEmptyRe`, MDL047)", "insight": "New **MDL047**: on a `RetrieveStmt.Where` (via `expressionToXPath`), regex a **module-qualified** name (one dot) directly `= empty`, with a leading boundary class that excludes `/` (so `Assoc/Attr = empty` — a valid attribute-over-association test — is NOT flagged) and `.`/word (so a 3-part enum literal tail isn't grabbed). Fix for the user: `[not(Assoc/Module.Target)]`. **Also covers page/widget datasource where-clauses** (`validateDatasourceXPathAssociationEmpty` on `DataSourceV3.Where`, shared regex via `xpathAssociationEmptyMatches`) — the ledger project hit it there first. Tests `TestValidateMicroflow_XPathAssociationEmpty`, `TestValidateDatasourceXPathAssociationEmpty`; repros `ledger-25-xpath-assoc-empty.fail.mdl` (retrieve) + `ledger-25-page-datasource-assoc-empty.fail.mdl` (datagrid). Ledger finding #25", "refs": ["#25"], "ce": ["CE0161"], "rules": ["MDL047"]} -{"area": "mdl/executor", "symptom": "Two sibling `dynamictext` widgets in a container render as `€ 310Last import: 7/24/2026` — concatenated with no separator, regardless of each one's RenderMode", "cause": "A Mendix DynamicText is always an inline ``; RenderMode does not make it block-level. Not a build error — a silent layout surprise, so a non-fatal advisory fits", "file": "`mdl/executor/validate_widgets.go` (`validateConsecutiveDynamicText`, called from `validateWidgetTreeIn`)", "insight": "New **MDL-WIDGET15** (info): scan each sibling list; emit once when a run of ≥2 adjacent **inline** dynamictexts occurs. **Only H1–H6 are block-level** (`headingRenderModeRe`) — `Text`/unset AND `Paragraph` both render as an inline `` (verified on Mendix 11.12.1 + Atlas), so `inlineDynamicText` excludes only headings. A heading+subtitle pair is NOT flagged; a Paragraph+Paragraph pair IS (it fuses). Fix for the user: merge into one dynamictext, wrap each in a container, or use a **heading** RenderMode — NOT Paragraph. Info severity so it never fails the build. Test `TestValidateConsecutiveDynamicText`; repro `mdl-examples/bug-tests/ledger-27-consecutive-dynamictext.mdl`. **Lesson: verify Mendix render behavior empirically — `Paragraph` sounds block-level but isn't; don't infer `display` from a name.** Ledger findings #27/#29", "refs": ["#27", "#29"]} -{"area": "mdl/executor", "symptom": "`create association … from/to ` passes `mxcli check --references` and `mxcli exec` creates it, but `mx check` rejects it with **CE6771** \"It is not possible to create associations to/from View Entities.\" Both directions are invalid", "cause": "Associations to/from a view entity are statically impossible, but the reference checker only validated that the endpoint modules exist — it never inspected whether an endpoint was a view entity", "file": "`mdl/executor/validate.go` (CreateAssociationStmt case) + `mdl/executor/cmd_entities.go` (`isViewEntity`)", "insight": "Resolve both endpoints via `findEntity`; if either `isViewEntity` (Source `DomainModels$OqlViewEntitySource`, or OqlQuery/SourceDocumentRef set), reject with CE6771 and point at a non-persistent entity carrying a real reference. Same-script endpoints are skipped (validated on their own statement). **References-mode only** (needs the domain model). Tests `TestIsViewEntity`; example `mdl-examples/bug-tests/ledger-41-view-entity-association.mdl`. Verified on Mendix 11.12.1. Ledger finding #41", "refs": ["#41"], "ce": ["CE6771"]} -{"area": "mdl/executor", "symptom": "A `create or modify entity` that omits (drops) an INDEXED attribute leaves the entity's index orphaned — its column references a GUID that no longer exists. `mxcli` prints only the attribute-drop warning; `mx check` then **CRASHES loading the project** with `System.AggregateException` \"The given key '' was not present in the dictionary\" (DESCRIBE shows a dangling `index ()`)", "cause": "Two parts. (1) The executor rebuilt the entity from the statement (no index clause → empty `entity.Indexes`), so the existing index wasn't reconciled against the surviving attributes. (2) The write path: `entityToGen` produced an **empty, untouched** Indexes PartList, which the codec treats as \"clean\" and passes the raw (orphaned) index through — an empty typed list does NOT override raw bytes for an existing element", "file": "`mdl/executor/cmd_entities.go` (`reconcileDroppedIndexes`, before `UpdateEntity`) + `mdl/backend/modelsdk/domainmodel_alter.go` (`UpdateEntity` empty-index dirtying)", "insight": "(1) When the statement lists no indexes, carry existing indexes forward, pruning columns for dropped attributes (attr IDs survive by name via #13) and dropping empty indexes — a **partial** drop (`index (A,B)` → drop B → `index (A)`) works via this non-empty list. (2) For the **all-removed** case, force the empty Indexes list dirty (`AddIndexes(NewIndex())` + `RemoveIndexes(0)` — `PartList.Remove` calls `markDirty`) so the codec re-emits it as empty, clearing the raw orphan. **Codec insight: an untouched empty PartList is `clean` → raw passthrough; only a *dirtied* list (even if empty) overrides raw for an existing element.** Tests `TestReconcileDroppedIndexes`; example `mdl-examples/bug-tests/ledger-39-drop-indexed-attribute.mdl`. **Verified end-to-end: `mx check` → 0 errors on Mendix 11.12.1** (previously crashed). Ledger finding #39", "refs": ["#13", "#39"]} -{"area": "mdl/executor", "symptom": "`retrieve … where [id = $Var]` (constraining on the object id) passes `mxcli check` but fails the build with **CE0161** \"Error(s) in XPath constraint\" — whether `$Var` is String or Long. Mendix XPath has no id operator reachable from a microflow expression", "cause": "No check inspected retrieve constraints for an id comparison; `id` is a reserved member so it resolves loosely", "file": "`mdl/executor/validate_microflow.go` (`checkXPathIdConstraint`/`xpathIdConstraintRe`, MDL048)", "insight": "New **MDL048**: on `RetrieveStmt.Where`, regex a bare `id` (word-boundaried, case-insensitive) before a comparison, **capturing the operand**, and flag only when the operand is a VALUE — a `$`-var whose kind is a primitive (in `varKinds`; objects aren't) or a string/number literal. **Comparing `id` to an OBJECT variable (`[id != $obj]`, the valid \"exclude self\" pattern) is NOT flagged** — verified valid on mx check; an over-broad `\\bid\\b\\s*[=…]` regex false-positived `16-xpath-examples.mdl`'s exclude-self example. Fix for the user: a marketplace GUID action (GetObjectByGuid), **or** expose the id as a String on a view entity (`cast(id as string) as ObjectId`) and constrain on that String column. Test `TestValidateMicroflow_XPathIdConstraint`; repro `mdl-examples/bug-tests/ledger-42-retrieve-by-id.fail.mdl`. **Lesson: `[id = $x]` splits on the operand type — value → CE0161, object → valid; a checker that ignores the operand mislabels the valid case.** Ledger finding #42", "refs": ["#42"], "ce": ["CE0161"], "rules": ["MDL048"]} -{"area": "mdl/executor", "symptom": "A call argument bound to an association-object path (`call M.Consume(B = $Edit/M.Edit_Budget)` — the object reached over an association) passes `mxcli check` but fails the build with **CE0117** \"Error(s) in expression.\" An attribute value over the same association (`$Edit/M.Edit_Budget/Name`) is fine", "cause": "Mendix does not treat an association path as a value — it must be materialized (`retrieve`) first. No check inspected call arguments for an association-object path", "file": "`mdl/executor/validate_microflow.go` (`checkAssociationObjectArgs`/`exprIsAssociationObjectPath`, MDL049; wired for CallMicroflowStmt + CallNanoflowStmt)", "insight": "New **MDL049**: an argument whose value is an `AttributePathExpr` whose FINAL segment is module-qualified (a `.` → an association, yielding an object) is flagged; a final bare segment (an attribute) is not. Fix for the user: `retrieve $x from $Edit/M.Edit_Budget;` then pass `$x`. Test `TestValidateMicroflow_AssociationObjectArg`; repro `mdl-examples/bug-tests/ledger-44-assoc-path-as-value.fail.mdl`. Ledger findings #43/#44", "refs": ["#43", "#44"], "ce": ["CE0117"], "rules": ["MDL049"]} -{"area": "mdl/executor", "symptom": "~~MDL050 \"format function + association navigation\"~~ **REMOVED — it was a false positive.** MDL050 flagged `formatDateTime($obj/Mod.Assoc/Date, …)` and `formatDecimal(…) + $obj/Mod.Assoc/Attr` as CE0117. Re-verification against `mx check` on 11.12.1 (once the real #48 root cause — the dropped association target-entity step — was fixed) showed the premise was wrong on BOTH cases", "cause": "The earlier \"format-function + association\" correlation was an artifact of TWO unrelated bugs, neither of which is about format functions: (1) **association navigation dropped its target-entity step** → CE0117 for ANY `$obj/Assoc/Attr` in an expression (see the \"#48 root cause\" row above), which happened to include the formatDateTime example; (2) **`formatDecimal($x, 2)` fails CE0117 on a PLAIN local decimal** with no association at all — a `formatDecimal`-signature bug, not an association issue. With (1) fixed, `formatDateTime($obj/Assoc/Date, …)` builds clean → MDL050 rejected valid code", "file": "`mdl/executor/validate_microflow.go` (removed `checkFormatWithAssociation`/`exprHasRenderFunc`/`exprHasAssociationNav`/`renderFuncsIncompatibleWithAssoc`)", "insight": "Deleted the check, its call sites, its test (`TestValidateMicroflow_FormatWithAssociation`), and its bug-test. **Lesson (reinforced): even after \"reproducing the failing neighbours\", a rule can still be mis-premised if the neighbours share a DIFFERENT hidden bug — here the missing entity step. Verify a check is still valid after every related write-path fix; a correlation-based rule is fragile.** The remaining real issue — `formatDecimal($x, precision)` failing CE0117 regardless of associations — is a **separate open finding** (likely a wrong function signature/arity; investigate against `mx` and fix in the executor or flag via the function checker). Ledger finding #48 (root cause corrected; MDL050 retired)", "refs": ["#48"], "ce": ["CE0117"], "rules": ["MDL050"]} -{"area": "mdl/executor", "symptom": "An action property on a pluggable widget — e.g. `onClick: microflow …` on a DataGrid2 — passes `mxcli check --references` AND `mx check`, builds and runs, but is **silently discarded**: `DESCRIBE PAGE` omits it and the model round-trips minus the action, with no error or warning", "cause": "The .def.json **generator** never emitted an `action` operation for `type=\"action\"` properties (across 42 generated defs, no action operation existed at all). The writer reads only `propertyMappings` from the def, so with no action mapping it had nothing to write. The `.mpk` correctly declares the action slot; the gap is purely in generation", "file": "`mdl/executor/widget_defs.go` (`GenerateDefJSON` `case \"action\"` + `actionSourceForKey`); `mdl/executor/widget_engine.go` (`resolveMapping` `case \"OnChange\"`; `WidgetDefGeneratorVersion` bump 12→13)", "insight": "Emit an `action` `PropertyMapping` for the action slots MDL can author: `onClick`→source `OnClick` (reads the widget's `Action` property, i.e. the `onClick:`/`Action:` alias) and `onChange`→source `OnChange`. The engine's **existing** `applyOperation \"action\"` → `builder.SetAction` path (nil-guarded) then serializes the `ClientAction` with its parameter mapping. Non-authorable action slots (DataGrid2 `onSelectionChange`/`onConfigurationChange`) return `\"\"` from `actionSourceForKey` → no mapping (no MDL surface yet). Bump the generator version so existing projects regenerate. Tests `TestGenerateDefJSON_ActionMapping`; example `mdl-examples/bug-tests/ledger-67-pluggable-widget-action.mdl`. **Verified end-to-end: `mx check` → 0 errors on 11.12.1**, and the DataGrid2 persists the onClick microflow action with its `Thing: $currentObject` mapping (the raw page unit carries `L67.OnClick` + `L67.OnClick.Thing`). Ledger finding #67 (write path)", "refs": ["#67"]} -{"area": "mdl/executor", "symptom": "**General guard for the #67 class:** a real widget property that the generator doesn't map to a write path could be silently dropped (or, via an alias, slip through) with no diagnostic — the same failure mode as #67 for any unmapped property type (`expression`, `icon`, a future type, an action slot with no MDL surface)", "cause": "The `WidgetDefinition.KnownProperties` field + the `MDL-WIDGET06` \"recognized but not persisted\" path already existed, but the **generator never populated `KnownProperties`**, so the warning never fired: an unmapped property either false-errored as MDL-WIDGET01 \"no such property\" or (via an alias like `Action`) passed silently", "file": "`mdl/executor/widget_defs.go` (`GenerateDefJSON` — populate `KnownProperties`); `WidgetDefGeneratorVersion` bump 13→14", "insight": "Compute `KnownProperties` purely from the two artifacts mxcli already has — **every `.mpk`-declared property key with no mapping in the generated def** (subtract PropertyMappings + aliases + ChildSlots + ObjectLists + mode mappings from the full key set). No per-widget knowledge. The existing `knownUnmappedProperties`/MDL-WIDGET06 check then WARNS \"recognized but not persisted — the value will be dropped.\" **Three-way discrimination verified on DataGrid2 (11.12.1): a mapped property (`onClick`) is silent, a real-but-unmapped one (`rowClass`) warns MDL-WIDGET06, a truly-unknown one (`totallyBogusProp`) still errors MDL-WIDGET01.** Test `TestGenerateDefJSON_KnownPropertiesUnmapped`. **This is the tester's suggested general check — it catches the whole class without guessing which widgets support what; the `action` mapping remains the specific fix for onClick.** Ledger finding #67 (general guard)", "refs": ["#67"]} -{"area": "mdl/executor", "symptom": "**DESCRIBE read gap (follow-up to #67):** after the write fix, `DESCRIBE PAGE` still omitted a DataGrid2's `onClick` action — a describe round-trip silently lost it", "cause": "The datagrid2 describe parse/output paths read datasource + columns + paging but never read the widget-level action; the param-aware `renderClientActionMDL` reader (used for button/onchange) was never called for a pluggable widget's action", "file": "`mdl/executor/cmd_pages_describe_pluggable.go` (`customWidgetPropertyActionMap`), `cmd_pages_describe.go` (`rawWidget.OnClick`), `cmd_pages_describe_parse.go` (datagrid2 branch), `cmd_pages_describe_output.go` (datagrid2 emit)", "insight": "New `customWidgetPropertyActionMap` returns the raw `Forms$*ClientAction` map for a CustomWidget property (NoAction → nil); the read is wired in **two** describe paths — the `datagrid2` branch AND the **generic `pluggablewidget '…'` branch** (`!isKnownCustomWidgetType`, where **CustomChart — the finding's actual widget — is described**). Each renders it via `renderClientActionMDL` (param-aware) into `rawWidget.OnClick`; the output emits `onClick: ` (the generic branch's guard also fires on `w.OnClick != \"\"` so an action-only widget still gets its `pluggablewidget` header). **Verified end-to-end on BOTH DataGrid2 and CustomChart: describe → re-exec → `mx check` 0 errors, round-trip preserves the full `onClick: microflow …(param: $currentObject)` including the parameter mapping** (CustomChart quotes the param name, `\"Data\":`, which re-parses fine). Test `TestCustomWidgetPropertyActionMap`. Ledger finding #67 (DESCRIBE read gap closed for datagrid2 + generic pluggable/CustomChart)", "refs": ["#67"]} -{"area": "mdl/executor", "symptom": "Two loops in the same microflow reusing the same iterator name (`loop $R in … end loop; loop $R in … end loop`) pass `mxcli check` but `mx check` fails with **CE0111** \"Duplicate variable name 'R'.\" at Loop", "cause": "A Mendix loop iterator is scoped to the **whole microflow**, not to its loop — so the second loop re-creates an existing variable. No check tracked loop iterator names across a microflow", "file": "`mdl/executor/validate_microflow.go` (`checkDuplicateLoopVariables`, MDL052)", "insight": "New **MDL052**: walk the microflow body (recursing through if/case/while/loop bodies) collecting `LoopStmt.LoopVariable` names; flag any name used by a second loop. Catches sequential AND nested reuse (a nested loop reusing an outer iterator is also CE0111). Distinct iterators, a single loop, and the same name across DIFFERENT microflows are fine. Fix for the user: give each loop a distinct iterator (`$R`, `$C`, `$M`). Test `TestValidateMicroflow_DuplicateLoopVariable`; repro `mdl-examples/bug-tests/ledger-64-duplicate-loop-variable.fail.mdl`. Ledger finding #64", "refs": ["#64"], "ce": ["CE0111"], "rules": ["MDL052"]} -{"area": "mdl/executor", "symptom": "A `break` nested inside an `if`/`case` within a loop passes `mxcli check` but serializes a **dangling sequence-flow reference** — `mx check` then **CRASHES loading the project** with an unhandled `System.AggregateException` (\"key … not present in the dictionary\"), an unrecoverable failure. A break placed **directly** in the loop body serializes fine", "cause": "The flow builder (`addBreakEvent`) creates the Break event but the sequence flow connecting it from inside a conditional dangles — a write-path (flow-graph serialization) bug, still open. `break` directly in the loop body wires correctly", "file": "`mdl/executor/validate_microflow.go` (`loopBodyHasConditionalBreak`/`stmtsContainBreak`, MDL051) — **interim check**, pending the serialization fix", "insight": "New **MDL051**: on a `LoopStmt`, scan its body for a `break` inside an if/case/inheritance-split (not descending into nested loops — a nested loop traps its own break); a direct-child break is not flagged. Since the pattern currently produces a *crash*, a check-time rejection is a strict improvement. Fix for the user: a guard variable (`declare $Done Boolean = false; … if not($Done) then … set $Done = true`) — **verified clean on mx check**. Test `TestValidateMicroflow_ConditionalBreak`; repro `mdl-examples/bug-tests/ledger-52-break-in-conditional.fail.mdl`. Verified on 11.12.1: conditional break → KeyNotFound crash, direct break + guard-var workaround → 0 errors. **The real fix is the break-in-conditional flow serialization (write path) — MDL051 is the interim guard.** Ledger finding #52", "refs": ["#52"], "rules": ["MDL051"]} -{"area": "mdl/executor", "symptom": "A datagrid **column** with an explicit empty caption (`Caption: ''`) passes `mxcli check` but `mx check` rejects the page with **CE0463** \"The definition of this widget has changed\" on the Data grid 2 (error points at the widget version, not the caption). Omitting the caption, or a non-empty string, both build clean", "cause": "The pluggable widget engine's column-header fallback treated a **present-but-empty** header property as \"has header\" and skipped the attribute-name default — so `Caption: ''` emitted an empty header (which Studio Pro rejects) while an **absent** caption got the fallback. The keyword datagrid path already handled it (`if caption == \"\" { caption = col.Attribute }` in `datagrid_column.go`)", "file": "`mdl/executor/widget_engine.go` (`applyColumnHeaderFallback`)", "insight": "**Write-path fix.** Detect an empty header (a `texttemplate` op with empty `TextTemplate` and no `Parameters`) and treat it like an absent one: fill it **in place** with the bound attribute's leaf name (not appended — that would duplicate the `header` prop). A header WITH params (`Caption: '{1}'`) is left untouched. Result: `Caption: ''` now behaves like omitting it. **Round 2 (custom-content columns):** the first fix left a column with **no bound attribute** (an action/custom-content column) untouched — it had nothing to derive a header from, so an empty OR absent caption still tripped CE0463 (a custom-content column requires a non-empty header). Fixed by falling back to the **column's own name** when there's no attribute, and **gating the whole fallback on the item template having a `header` slot** (`mapping.ItemProperties`) so header-less object-list items (chart series, accordion groups) are never given a spurious header. `applyColumnHeaderFallback(spec, columnName, hasHeaderSlot)`. Test `TestApplyColumnHeaderFallback` (cases 1–8); examples `ledger-54-empty-column-caption.mdl` + custom-content verified via exec. **Verified: `mx check` → 0 errors on 11.12.1 for attribute columns AND custom-content columns (empty + absent caption)** (previously CE0463). Ledger finding #54 (custom-content columns)", "refs": ["#54"], "ce": ["CE0463"]} -{"area": "mdl/executor", "symptom": "`CREATE CONFIGURATION` (or any `ALTER SETTINGS`) reports success and `mx check` passes, but Studio Pro throws `System.InvalidOperationException: Sequence contains no matching element` at `MprProperty.cs:25` when the changed unit is opened (e.g. from the version-control status grid). Silently, the same write also resets **HttpPortNumber/ServerPortNumber to 0** on every *existing* configuration", "cause": "Three storage-name/enum defects in the settings write, all invisible to mxbuild (its deserializer tolerates unknown properties; Studio Pro resolves each stored property against the type's property list and throws when there is no match). (1) `createConfiguration` hardcoded `DatabaseType: \"HSQLDB\"` — the enum member is `Hsqldb`. (2) The gen `Configuration` binds the ports as `RuntimePortNumber`/`AdminPortNumber` (SDK names) while Studio Pro stores `HttpPortNumber`/`ServerPortNumber`, so the read returned 0 and the overlay wrote that 0 back. (3) Mendix renamed the runtime Java version property between 11.6 (`JavaVersion` = `\"Java21\"`) and 11.12 (`JavaMajorVersion` = `\"21\"`); mxcli wrote the 11.6 name unconditionally, leaving `JavaMajorVersion` stale and adding a property 11.12 does not define", "file": "`mdl/executor/cmd_settings.go` (`settingsDatabaseType`, `createConfiguration` defaults), `mdl/backend/modelsdk/settings_read.go` (`rawInt`, `javaVersionOf`), `mdl/settingsoverlay/settingsoverlay.go` (`JavaVersionKey`/`SetJavaVersion`, `newServerConfiguration`), `sdk/mpr/parser_settings.go` + `writer_settings.go`", "insight": "Canonicalise enum-valued settings against `generated/metamodel` and reject the rest (executor **and** `mxcli check`, via a `settingsKind*` entry so the drift guard covers it). Read version-renamed properties off the stored document and write them back to the key they came from — **never invent a key the document does not already have** (the same reasoning removed the hardcoded `Tracing: nil` from the no-sibling fallback: 11.12 spells it `OpenTelemetry`). **Diagnose without Studio Pro**: dump the `Settings$ProjectSettings` unit before and after the command and diff key-by-key against the project `mx create-project` produced — the write must be purely additive. A \"no matching element\" *property* lookup means a key Mendix does not know; an enum member mismatch is a different exception. Repro: `create configuration 'X'` on an 11.12 project. Issue #759", "refs": ["#759"]} -{"area": "mdl/executor", "symptom": "`mxcli check … --references` reports **\"All references valid\" / \"Check passed!\"** for a script whose GRANT names a module role from a different module than the document; `exec` then fails with **CE0148** — after the preceding statements have already been applied, leaving the project half-modified", "cause": "The guard (`checkDocumentAccessRolesSameModule`) existed and was wired into all five exec paths, but **no validate path ever called it**. mxcli does not run a script in a single transaction, so a failure that only surfaces at exec time is exactly what a pre-flight check exists to prevent", "file": "`mdl/executor/validate_grant_roles.go` (`ValidateGrantRoles`, MDL-GRANT01), `mdl/executor/cmd_security_defaults.go` (`validateCrossModuleGrant`), wired in `cmd/mxcli/cmd_check.go`", "insight": "Reuse the existing exec-time guard from the **no-project** violations pass, covering all five document-access grants (microflow, nanoflow, page, OData service, published REST service). Take the document's module from the **statement's own qualified name**, not the resolved document — same comparison, and it works before the document exists (it is often created earlier in the same script). **Put it in the no-project pass, not under `--references`**: the check compares two names already in the script, so requiring `-p` withholds an answer mxcli can always give, and a plain `mxcli check` now catches it. **Generalisable — the shape to look for**: when exec rejects something a checker accepts, the bug is usually not a missing rule but a rule wired into only one of the two paths — grep the guard's callers before writing a new one. Repro `mdl-examples/bug-tests/836-check-cross-module-grant.fail.mdl`; verified end-to-end (check exits 1 naming the statement and CE0148, project left untouched; same-module variant still passes and gives 0 errors under mx check). Issue #836", "refs": ["#836"], "ce": ["CE0148"]} -{"area": "mdl/executor", "symptom": "`DESCRIBE MICROFLOW` emits **`on error rollback`** on activities authored with no error-handling clause at all, growing the diff on every round-trip. No checker flags it — `\"Rollback\"` is structurally valid, so `mx check` and every mxcli validator pass", "cause": "`Rollback` is what `convertErrorHandlingType(nil)` stores for an activity with no clause **and** what the parser falls back to when `ErrorHandlingType` is absent from the BSON. The stored value therefore cannot distinguish an authored clause from the default, and read-back guessed \"authored\"", "file": "`mdl/executor/cmd_microflows_show_helpers.go` (`formatErrorHandlingSuffix`)", "insight": "Drop the `Rollback` case so it falls through to no suffix. **The asymmetry is the whole argument**: omitting it is lossless (re-executing stores `Rollback` again, so the model is unchanged), while emitting it is lossy in the direction that matters — it puts a clause in the user's script that they never wrote. `Continue` / `Custom` / `CustomWithoutRollback` are never defaults, so they still round-trip. **Generalisable — the shape to look for**: when a formatter renders an enum whose zero/fallback value is also a legal authored value, read-back cannot invert the write; render only the values that are *never* defaults. Ask \"what does the parser fall back to?\" before trusting a stored enum to mean the author chose it. Repro `mdl-examples/bug-tests/840-describe-invents-on-error-rollback.mdl`; verified end-to-end (describe → exec → describe byte-identical, `mx check` 11.13.0 0 errors). Issue #840", "refs": ["#840"]} -{"area": "mdl/executor", "symptom": "A `create rest client` operation reports success, but `describe rest client` omits `Query:`, `Parameters:` and `Headers:` and always prints `Response: none`. BSON shows the query parameters/headers stored **correctly** while `ResponseHandling` is `Rest$NoResponseHandling` — the response mapping is gone. `mx check` passes (0 errors), so nothing anywhere complains", "cause": "**Two unrelated defects with one symptom.** *Write*: `model.RestClientOperation` documents `BodyType`/`ResponseType` as UPPER-case tokens and every consumer compares against that spelling, but the MDL executor stored the visitor's lower-case source text — so `op.ResponseType == \"MAPPING\"` never matched and the mapping fell through to the else-branch, which legitimately writes `NoResponseHandling`. *Read*: `restOperationFromGen` populated only Name/HttpMethod/Path/Timeout and type-asserted `*genRest.RestParameter` for **both** parameter lists, while the writer emits `Rest$OperationParameter` and `Rest$QueryParameter` — two different gen types, so both assertions failed silently", "file": "`mdl/executor/cmd_rest_clients.go` (`buildRestClientOperation` normalization + `checkInlineMappingBody`), `mdl/backend/modelsdk/integration_read.go` (`restOperationFromGen` + response/body/mapping-tree readers), `mdl/backend/modelsdk/consumed_rest_write.go` and `modelsdk/mpr/serialize_web_services.go` (`EqualFold`), `mdl/executor/validate_rest_mapping.go` (MDL-REST01)", "insight": "Normalize with `strings.ToUpper` at the one place the AST becomes the semantic model, and make the two serializer comparisons `EqualFold` so the landmine is not left armed for the next producer. **Generalisable — the shape to look for**: a case-sensitive comparison against a *documented-but-unenforced* string constant, where the non-matching branch is a **legitimate** outcome. Nothing errors, because \"no response handling\" is a real thing an operation can have — the else-branch launders a producer/consumer mismatch into a plausible-looking model. Grep every comparison against the constant, expect one per engine, and check whether the false branch is silent. **Second shape**: a reader that type-asserts one concrete type for two lists the writer builds from two *different* types; the assertion fails to `ok=false` and `continue`s, so a stub reader is indistinguishable from an empty document. The pre-existing round-trip test even created a query parameter — but only asserted the operation *count*, never that the parameter survived. **Third, separate half (#843's headline)**: `Response: mapping Mod.IMM_X` names an import mapping **document**, which Mendix cannot reference — `Rest$RestOperationResponseHandling` has exactly two implementations, inline and none. The clause parsed, contributed no entries, and was written as \"none\". Now refused at exec *and* `mxcli check` (MDL-REST01, no project needed). **Note** `Rest$QueryParameter` stores no DataType at all, so the MDL type is decorative and `describe` re-emits every query parameter as `String` — do not \"fix\" that by inventing the authored type back (see the #840 row). Repros `mdl-examples/bug-tests/843-rest-response-mapping.mdl` + `843-rest-response-mapping-no-body.fail.mdl`; verified end-to-end (`mx check` 11.13.0 0 errors, BSON now `Rest$ImplicitMappingResponseHandling` with a full `ImportMappings$ObjectMappingElement` tree, describe → exec → describe byte-identical). Issue #843", "refs": ["#840", "#843"]} -{"area": "mdl/executor", "symptom": "`loop { if then break; }` where the `if` is the **last** statement builds a Decision with only its `true` outgoing flow (→ break). `mxcli check` passes but `mx check` reports **CE0079** \"the 'false' condition value should be configured in properties for an outgoing sequence flow\", and the microflow won't deploy. (Distinct from the earlier #791 crash — that was a dropped Break/Continue *event*; this is a missing *flow*.) `continue` and break-not-last behaved likewise", "cause": "The loop-body flow builder (`addLoopStatement`) is a simplified copy of `buildFlowGraph` that connected body statements with a plain `newHorizontalFlow` and **never honoured the deferred `nextFlowCase`** a merge-less split leaves for its FALSE branch. So the split's false case was dropped: mid-body it wired the next statement with no case; as the last statement it wired nothing at all", "file": "`mdl/executor/cmd_microflows_builder_control.go` (`addLoopStatement` body loop)", "insight": "Mirror `buildFlowGraph`: track `pendingCase` between body statements and apply it to the connecting flow; then, for a leftover `pendingCase` at the end of the loop body (a decision whose non-terminal branch falls off the end), synthesize a **ContinueEvent** and wire the split's false flow to it — the valid Mendix representation of \"didn't break/return → next iteration\". **Trap**: the check-time acceptance test (`TestValidateMicroflow_ConditionalBreakAccepted`) only asserted MDL051 doesn't fire — it never ran `mx check` on the *output*, so the CE0079 microflow shipped green. Assert the produced BSON, not just that check accepts the source. Repro `mdl-examples/bug-tests/ledger-52-break-in-conditional.mdl`; verified raw `mx check` 0 errors (was 1× CE0079) and the split now carries both a true→Break and a false→Continue flow. Ledger #52", "refs": ["#52", "#791"], "ce": ["CE0079"], "rules": ["MDL051"]} -{"area": "mdl/executor", "symptom": "A workflow containing a standalone `annotation '...'` writes a project Mendix **cannot load**: `System.InvalidOperationException: Type ...Workflows.Model.Annotation does not contain a constructor with a parameter of type ...Workflows.Model.Flow`. Not a build error — Studio Pro will not open the project and `mx check` dies before validating anything. `mxcli check` passed and `exec` succeeded", "cause": "mxcli writes the annotation into the workflow's **activity flow**. Mendix loads that list by constructing every child with a `Flow` parent, and no annotation type takes one: `Workflows$Annotation` carries only `Description` (it attaches to a Flow) and `Workflows$FloatingAnnotation` (the canvas sticky note, which has exactly the `RelativeMiddlePoint`/`Size` fields mxcli was already writing) is not a flow element either. **Placement is the defect, not the storage name** — swapping the `$Type` to FloatingAnnotation reproduces the identical error with the new type name", "file": "`mdl/executor/validate_workflow.go` (new `MDL-WF04`), `mdl/executor/cmd_workflows_write.go` (`execCreateWorkflow` guard + `hasStandaloneWorkflowAnnotation`), skill `.claude/skills/mendix/write-workflows.md` (which had documented the construct)", "insight": "**Refuse rather than emit an unopenable unit** — at check time *and* at exec time, because a user who skips `check` otherwise still loses the whole project. The correct container is not determinable from the gen model (no struct owns a `FloatingAnnotation` list) and CLAUDE.md's rule applies: when the BSON shape is unknown, get a Studio Pro reference rather than guess. **Generalisable**: verify a storage-name hypothesis by *swapping only the name* — if the error is byte-identical with the new type, the bug is where the element is attached, not what it is called. Repro `mdl-examples/bug-tests/it-15-workflow-annotation-refused.fail.mdl`; A/B: pre-fix binary writes it and `mx check` cannot load the project, fixed binary refuses and the project checks 0 errors. issuetracker #15 **Follow-up (CI):** three `-tags integration` round-trip tests asserted this construct *works* and went red on the guard. They exercised mxcli's own write → read → describe → re-execute loop, which a structurally invalid document survives — the loop never loaded the project in Mendix, so it proved nothing about validity. Re-settled by stubbing the guard and running real `mx check`: the project fails at \"Loading the mpr file\". The tests were pinning the defect, and now assert the refusal (`TestCreateWorkflow_StandaloneAnnotationRefused`). **Second instance in one PR** of a green test codifying a bug (see the `jump to` row) — when a pre-existing test contradicts a new guard, re-derive the ground truth from the layer the symptom lives in before believing either.", "refs": ["#15"], "rules": ["MDL-WF04"]} -{"area": "mdl/executor", "symptom": "A page datasource navigating an association writes `DestinationEntity: \"\"`, and the project becomes **unloadable**: `An error occurred when trying to set the 'DestinationEntity' property of a Entity ref step ... ---> System.ArgumentNullException` at `EntityRefStep.set_DestinationEntityId`. Studio Pro will not open it and `mx check` dies before validating anything. `mxcli check` and `exec` both succeed", "cause": "`resolveAssociationDestination` resolves both ends via `entityQNByID`, which only sees the **project's own** domain models — an association ending in a **System** entity (`from W.Issue to System.Workflow`) yields `\"\"` for that side. The context then matched neither end, and the fallback `return childEntity` returned the empty one. An empty by-name reference is not \"absent\", it is a reference Mendix resolves to null", "file": "`mdl/executor/cmd_pages_builder_v3.go` (`resolveAssociationDestination` fallbacks + a hard guard in the `association` case of `buildDataSourceV3`)", "insight": "Prefer whichever end actually resolved and is not the context; then **refuse** an unresolved destination rather than write it, pointing at the explicit `Assoc/Module.Entity` form (verified to build 0 errors — it is the construct the reporter had abandoned). **Narrower than reported**: the finding blamed *nesting*, but a one-step probe with the same association reproduces it identically — nesting was incidental. Always re-derive the trigger with the smallest case before fixing the reported shape. **Generalisable**: a resolver that returns `\"\"` on failure will silently produce a null by-name reference; make the write path refuse empty rather than trusting the resolver. Repro `mdl-examples/bug-tests/it-14-assoc-destination-entity.mdl`; A/B: pre-fix binary leaves the project unopenable, fixed binary refuses and the project checks 0 errors. issuetracker #14", "refs": ["#14"]} -{"area": "mdl/executor", "symptom": "`describe workflow` renders a plain `jump to Review;` as `jump to Review comment 'Review';` — a comment clause the author never wrote, which then round-trips back into the model as a real caption", "cause": "`buildJumpTo` defaults the activity's `Caption` to the **target name**, and the DESCRIBE emitter echoed `Caption` unconditionally (falling back to the activity `Name` when empty). Both are derived values carrying no authored information", "file": "`mdl/executor/cmd_workflows.go` (`JumpToActivity` case in `formatWorkflowActivities`)", "insight": "Emit `comment '...'` only when the caption is genuinely authored — non-empty **and** different from both the target name and the activity name. **Watch for tests that codify the bug**: `TestFormatJumpTo_CaptionCommentFormat` had a \"name fallback when caption empty\" case asserting the phantom comment, and two issue-619 quoting tests were incidentally coupled to it; a green suite was pinning the defect in place. Tests `mdl/executor/cmd_workflows_describe_test.go`, `mdl/executor/issue619_emitter_quoting_test.go`. issuetracker #16", "refs": ["#16"]} -{"area": "mdl/executor", "symptom": "A workflow `decision ''` referencing the context passes `mxcli check`, executes, then the build fails `[error] [CE0117] \"Error(s) in expression.\" at Decision 'Decision'`. `$WorkflowContext/X` (exact casing) works; `$workflowContext/X` — the spelling this repo's own skill documented — and `$Ctx/X` from the author's `parameter $Ctx:` header both fail", "cause": "mxcli always stores the context parameter as `WorkflowContext` and Mendix expressions are **case-sensitive**. `normalizeWorkflowContextExpr` already existed but was wired into `autoBindCallMicroflow` only, so `with (...)` mappings were normalized while a decision's condition was written through verbatim. Separately, the header's declared variable name was parsed into `ast.CreateWorkflowStmt.ParameterVar` and then **never consumed** — a field populated but read nowhere, so `$Ctx` resolved to nothing", "file": "`mdl/executor/cmd_workflows_write.go` (`contextExprNormalizer` + threading it through `autoBindActivitiesInFlow`), `mdl/executor/cmd_alter_workflow.go`, skill `.claude/skills/mendix/write-workflows.md` (whose examples were the failing spelling)", "insight": "One normalizer applied to **every** expression an author can write in a workflow — decision conditions, user task due dates and XPath targeting, wait-for-timer delays, call-microflow mappings — rather than a second point fix. The declared name is aliased onto the stored one (whole-word, so `$CtxItem` is not mangled) instead of documenting it as meaningless. **Generalisable**: grep for an AST field that is written by the visitor and read nowhere — that is a silently-discarded user intent, not dead code. Repro `mdl-examples/bug-tests/it-17-workflow-context-expression.mdl`; A/B: pre-fix binary writes it and `mx check` reports CE0117, fixed binary checks 0 errors. issuetracker #17", "refs": ["#17"], "ce": ["CE0117"]} -{"area": "mdl/executor", "symptom": "A page widget bound through an association — `Attribute: Issue_Assignee/Name` — passes `mxcli check`, executes, then fails `[error] [CE1613] \"The selected attribute 'IT.Issue.Issue_Assignee/Name' no longer exists.\"`. The error text is the raw MDL path glued onto the context entity. Same-module paths (`Issue_Project/Code`) work", "cause": "A domain model keeps associations in **two** lists. `Associations` holds intra-module ones (both ends BY_ID); an association targeting another module is a `DomainModels$CrossAssociation` in **`CrossAssociations`**, where only the local end is BY_ID and the remote end is the BY_NAME `ChildRef`. `associationEndpoints` searched only the first list, so every cross-module hop returned ok=false and the writer fell back to a flat attribute path instead of an `AttributeRef` with an `IndirectEntityRef` of steps", "file": "`mdl/executor/cmd_pages_builder_v3.go` (`associationEndpoints`, `resolveAssociationDestination`), `mdl/executor/widget_engine.go` + `cmd_pages_builder_input.go` (`resolveAssociationPathIn`, `storedSystemMemberName`)", "insight": "**Scope correction — the reported trigger was wrong.** The finding blamed the *System module*; a plain second app module reproduces it identically, so the trigger is cross-module. Fixing \"System\" alone would have left the commoner case broken — always re-derive the trigger with a neutral variant before fixing the reported one. Two sibling defects in the same finding: (a) a ComboBox's `Association:` was qualified with the module of its **own option list**, because the `DataSource:` mapping runs first and moves `pageBuilder.entityContext` — an association belongs to the *containing* entity, so it now resolves against the context saved at `Build` entry (`outerEntityContext`); (b) `CreatedDate: AutoCreatedDate` is the spelling mxcli **requires** when declaring an audit member, but the member is stored as `createdDate`, so binding the name you just declared failed — `storedSystemMemberName` now maps declared→stored. **Generalisable**: when a resolver reads one collection off a model object, check whether the model splits that concept across two (intra- vs cross-module, own vs inherited). Repro `mdl-examples/bug-tests/it-19-cross-module-attribute-path.mdl`; A/B on Mendix 11.12.1: pre-fix binary → 4 × CE1613, fixed binary → 0 errors. issuetracker #19", "refs": ["#19"], "ce": ["CE1613"]} -{"area": "mdl/executor", "symptom": "GRANT rejects members Mendix does recognise — `entity M.Label has no member(s) Issue_Label` for the non-owning end of an `OWNER Both` reference set, and `has no member(s) createdDate, changedDate` for audit members — and a `read * / write *` rule that looks complete still fails `[error] [CE0066] \"Entity access is out of date.\"`, so partial coverage is worse than none", "cause": "Two unrelated gaps in \"what counts as a member\". (a) `OWNER Both` makes an association a member of **both** ends, but the writer emitted the MemberAccess only for the FROM entity (`ParentID`) **and** `ReconcileMemberAccesses` independently applied the same FROM-only rule — so it stripped the entry back out on the next write even if the executor had added it. Two places had to agree. (b) Audit members are entity **flags** (`HasCreatedDate`/`HasChangedDate`), not entries in `entity.Attributes`, so the member walk never yielded them", "file": "`mdl/executor/cmd_security_write.go` (`execGrantEntityAccess`, `storedAuditMembers`, `otherModuleBothOwnerAssociations`), `mdl/backend/modelsdk/domainmodel_security_write.go` (`ReconcileMemberAccesses`)", "insight": "**Ask mxbuild what it wants instead of inferring symmetry.** Emitting a MemberAccess for `createdDate` seemed like the obvious fix for (b) — mxbuild **rejects** it with CE0066, and an entity storing audit members checks clean with no entry. So audit members are accepted as names but per-member rights on them are **refused with the reason** rather than silently dropped; only the `OWNER Both` association actually needed a new entry. **When a symptom has two spellings (a rejection and a build error), check whether they are one bug or two** — here they were two, and fixing them the same way would have introduced a new CE0066. **Generalisable**: a writer and a reconciler that both compute \"the expected member set\" are one invariant in two places; changing one alone is silently undone. Repro `mdl-examples/bug-tests/it-20-grant-member-coverage.mdl` (+ `it-20-grant-audit-member-rights.fail.mdl`); A/B on Mendix 11.12.1, same module same project: pre-fix binary → CE0066 + the bogus rejection, fixed binary → 0 errors. Controlled: the identical model with `OWNER Default` checks clean pre-fix, so the owner mode is the trigger. issuetracker #20", "refs": ["#20"], "ce": ["CE0066"]} -{"area": "mdl/executor", "symptom": "A loop's variable used after `end loop;` passes `mxcli check`, then `mx check` fails `[error] [CE0108] \"Variable 'item' is defined but not in scope at this location.\"` at the referencing activity. Applies to the **iterator** and to anything the body introduces (a `retrieve`, a `$X = create …`, a call output)", "cause": "Nothing tracked loop-variable *visibility*. MDL052 already covered the sibling rule — names are unique across the whole microflow (CE0111) — and the wording of that rule (\"scoped to the WHOLE microflow\") reads as if the variable is readable flow-wide. Uniqueness and visibility are different: the name is reserved everywhere, readable only inside the loop body", "file": "`mdl/executor/validate_microflow_loop_scope.go` (new `MDL053`, wired from `microflowValidator.validate`), skill `.claude/skills/mendix/write-microflows.md`", "insight": "Map each loop-scoped name to the loop whose **own** body introduces it (nested loops keep their own names), then walk the flow with the set of enclosing loops and flag any reference from outside the owner. **A name claimed by two loops is dropped, not reported** — that is the MDL052/CE0111 case, and without the guard the MDL052 negative example started failing for the wrong reason: the first loop's own use of `$R` was blamed on the second loop's claim. **Generalisable**: a rule keyed by variable *name* needs an ambiguity escape hatch whenever another rule exists precisely because names can collide. Both flavours verified against mxbuild 11.12.1 (2 × CE0108 in one probe). Repro `mdl-examples/bug-tests/loop-variable-out-of-scope.fail.mdl`; tests `mdl/executor/validate_microflow_loop_scope_test.go`. Found while working the sudoku findings, but **not** one of them — the numbered finding it was filed under is an app bug in that project, not an mxcli defect", "ce": ["CE0108", "CE0111"], "rules": ["MDL052", "MDL053"]} -{"area": "mdl/executor", "symptom": "A page bound to an attribute the entity **inherits** (`Person extends Administration.Account`, page binds `FullName`) passes `mxcli check --references` AND `mxcli lint`, then the real MxBuild fails `[error] [CE1613] \"The selected attribute 'TaskBoard.Person.FullName' no longer exists.\"` The message reads as a deletion; it never existed there", "cause": "Mendix stores a page's attribute reference against the entity that **declares** the attribute. `resolveAttributePath` qualified a bare name with `pb.entityContext` unconditionally, so a specialization that merely inherits the attribute got a dangling reference. Two independent resolvers had the same bug: the direct binding, and the final attribute of an association path (`resolveAssociationAttributePath`)", "file": "`mdl/executor/cmd_pages_builder_input.go` (`declaringEntityFor`, `entityAttributeOwners`), `mdl/executor/cmd_pages_builder_v3.go` (final attribute of the association path)", "insight": "Walk the generalization chain and qualify with the first entity that declares the name. **Fix both resolvers** — a probe that only tested the direct case would have shipped half a fix; the reporter's own table already showed the associated case failing, and it did still fail after the first patch. Unknown names keep today's context qualification rather than being re-pointed, and a cyclic chain terminates. **Watch the new dependency**: attribute resolution now consults the domain models, and `getDomainModels` panics on a nil backend — several unit tests build a `pageBuilder` with neither backend nor cache, so the lookup bails out early instead. A/B on Mendix 11.12.1: pre-fix binary → CE1613 for the inherited column and 0 errors for the own column; fixed binary → 0 errors for both shapes. Repro `mdl-examples/bug-tests/todo-12-inherited-attribute-on-page.mdl`; tests `mdl/executor/cmd_pages_builder_inheritance_test.go`. mxcli-todo #12", "refs": ["#12"], "ce": ["CE1613"]} -{"area": "mdl/executor", "symptom": "A decision written with **uppercase** keywords — `IF $T/Status != M.Status.Done AND $T/CompletedOn != empty` — passes `mxcli check` and then fails the build with `[error] [CE0117] \"Error(s) in expression.\"`, quoting the expression back with `AND` still uppercase. The same condition written with `=` builds fine, which makes it look like `!=` cannot be an operand of `AND`", "cause": "Mendix requires its word operators lowercase. A rebuilt `BinaryExpr` gets `strings.ToLower(e.Operator)`; a condition kept as an `ast.SourceExpr` (original text **plus** the parsed tree) returned `e.Source` verbatim and skipped it. The `=` form parses to a BinaryExpr and the `!=` form to a SourceExpr — hence the operator-shaped illusion", "file": "`mdl/executor/cmd_microflows_helpers.go` (`normalizeMendixOperatorCase`, applied in the `SourceExpr` branch)", "insight": "Lowercase `and/or/not/div/mod` in preserved source, leaving everything else byte-identical: a scanner that tracks single-quoted literals (with `''` escapes) and skips any word preceded by `.`, `/` or `$`, so `'AND'`, `M.Enum.And`, `$Task/Mod` and `$Android` are untouched. **The reporter's own probe table is the cautionary bit** — nine builds established a rule (\"any `!=` inside `AND` fails\") that was real in every observation and wrong about the cause, because every failing probe was uppercase and the control was not. When a table's rule tracks a token, check what ELSE differs between the rows. Reproduced and fixed against mxbuild 11.12.1: stored `AND` → CE0117, stored `and` → 0 errors. Repro `mdl-examples/bug-tests/todo-14b-uppercase-and-operator.mdl`; tests `TestNormalizeMendixOperatorCase`. mxcli-todo #14b", "refs": ["#14"], "ce": ["CE0117"]} -{"area": "mdl/executor", "symptom": "`CREATE DEMO USER` reports success and `SHOW PROJECT SECURITY` reports `Demo Users Enabled: true`, yet the running app has **zero** accounts — `SELECT Name FROM Administration.Account` returns 0 rows, there is no login page, and none of the row-level XPath rules are enforced", "cause": "A blank mxcli template ships with **Security Level: Off**, and with it off the runtime creates no accounts at all. The demo users are written to the model correctly; nothing connected the two facts, so the model said yes and the app said nothing", "file": "`mdl/executor/cmd_security_write.go` (`warnDemoUsersInert`, called after a successful create)", "insight": "Say it at the moment the user would otherwise believe it worked, and name the one statement that fixes it (`alter project security level prototype`). A *warning*, not a refusal: authoring demo users before raising the level is legitimate ordering. **Generalisable**: when a write succeeds but a project-level setting makes it inert, the write path is the only place with both facts in hand. Tests `TestWarnDemoUsersInert`. mxcli-todo #15", "refs": ["#15"]} -{"area": "mdl/executor", "symptom": "Wiring a microflow that takes parameters to a **BEFORE CREATE** event handler passes `mxcli check` (and `--references`), and the build then fails `[error] [CE7247] \"Microflow should not have parameters\" at Event handler of entity …`", "cause": "Mendix passes no object to a before-create handler — the object does not exist yet — so the handler is called with no arguments. Nothing compared the handler's moment against the microflow's signature; the pairing is only invalid for this one moment/event combination", "file": "`mdl/executor/cmd_entities.go` (`checkBeforeCreateHandlerHasNoParameters`, called from `buildEventHandlers`)", "insight": "Guard where the two paths converge — `buildEventHandlers` is shared by `CREATE ENTITY`'s inline handlers and `ALTER ENTITY ADD EVENT HANDLER`, so one check covers both. It refuses **before the model is written**, and the message carries the build code plus the way out (AFTER CREATE, which does receive the object). **A microflow created earlier in the same script is not readable back yet, so an unreadable microflow is skipped rather than refused** — mxbuild still catches the real case, and failing on the read would break legitimate scripts. Note this is an exec-time guard: `mxcli check` without a project cannot see the microflow's signature at all. A/B on 11.12.1: pre-fix binary writes it and mxbuild reports CE7247; fixed binary refuses, and both AFTER CREATE and a no-parameter BEFORE CREATE still work. Tests `mdl/executor/cmd_entities_before_create_test.go`. mxcli-todo #14a", "refs": ["#14"], "ce": ["CE7247"]} -{"area": "mdl/executor", "symptom": "A wrong `icon:` reference passes `mxcli check` (including `--references`) and first surfaces as a **build** error: `[error] [CE1613] \"The selected custom icon 'Atlas_Core.Atlas_Filled.no-such-icon' no longer exists.\" at Action button 'btnBad'`", "cause": "Nothing resolved the reference — the name was written straight through to BSON. Icon-collection lookup existed only for `show`/`describe` (`cmd_iconcollections.go`), never in a validation path", "file": "`mdl/executor/validate_icon_refs.go` (`validateIconRefs`), wired into `validateProgram`", "insight": "Index the project's icon collections once per run and resolve every reference in the program. **Put it in the `--references` pass, not the no-project one** — the collections are documents *in the project* (a blank 11.13 app ships three, ~770 icons), so unlike the #836 grant check there is genuinely nothing to resolve against without `-p`. **Report the two failures differently**: an unknown *icon* in a known collection gets near-match suggestions plus `describe icon collection `; an unknown *collection* gets the list of collections that exist, because that is where the typo usually is. **Generalisable — the shape to look for**: a string property that names a model element but is stored as a plain string is invisible to every reference checker; grep for properties whose value is a qualified name yet whose type is `string`. **Test the silence, not just the noise**: the risk in a new check rule is false positives, so sweep the repo's own examples (all 9 `mdl-examples` scripts using icons) before shipping — a rule that fires on valid input is worse than the gap it closes. **Repro cannot be a `.fail.mdl`**: `make check-mdl` runs `mxcli check` with no `-p`, so a bad-icon script would pass there and be reported as a negative test unexpectedly passing; the repro carries valid icons and the rejection cases live in unit tests. Repro `mdl-examples/bug-tests/icon-reference-validation.mdl`; verified end-to-end (bad reference reported before any write; `mx check` 11.13.0 0 errors on the valid script)", "refs": ["#836"], "ce": ["CE1613"]} -{"area": "mdl/executor", "symptom": "`mxcli check` reports `✓ Syntax OK` / `Check passed!`, then `mxcli exec` on the same script fails partway through with `failed to resolve page: page not found: Module.Page` — a button targeting a page the script creates further down. `exec` is not transactional, so the statements before the failure are already written to the .mpr", "cause": "Page references are resolved in statement order at exec time. `check --references` already had an ordered pass (`validateForwardPageRefs`), but it needs `-p`; plain `check` had nothing, and plain `check` is what gets run", "file": "`mdl/executor/validate_page_order.go` (`ValidateScriptPageOrder`, MDL-PAGE01), wired in `cmd/mxcli/cmd_check.go` beside the other project-free validators", "insight": "The soundness argument is what makes this work without a project: a **plain** CREATE later in the script would fail if the page already existed, so the script itself asserts the page does not exist yet and the earlier reference cannot resolve against the project either. `CREATE OR MODIFY`/`OR REPLACE` assert nothing, so they stay with `--references`, which can look. **Generalisable**: an ordering rule that seems to need project state often does not, once you find the statement that already asserts what you were going to look up. Two things the diagnostic must say and does: a cycle cannot be fixed by ordering (create one page without the linking widget, add it with `ALTER PAGE … INSERT`), and commit before executing a large script. Verified against all `mdl-examples/**/*.mdl` for false positives (0). Tests `mdl/executor/validate_page_order_test.go`, example `mdl-examples/bug-tests/todo-9-forward-page-reference.fail.mdl`. mxcli-todo #9", "refs": ["#9"]} -{"area": "mdl/executor", "symptom": "`create non-persistent entity X ( A: String not null error '…' )` (or `unique`) passes `mxcli check` AND `mxcli exec`, then the build fails **CE0070** \"Validations rules are not allowed on entity 'X', because it is not persistable\"", "cause": "`not null` / `unique` ARE validation rules — Studio Pro models \"required\" and \"uniqueness\" as rules on the entity, not as column constraints — so Mendix rejects them on a non-persistable entity. Nothing in mxcli connected the attribute constraint to the entity's persistence kind", "file": "`mdl/executor/cmd_enumerations.go` (`validateNPEValidationRules`, called from `ValidateEntity`)", "insight": "Add **MDL054**, error severity, fired from the CREATE path only. **Establish the construct matrix against mxbuild before writing the rule, not from the issue text** — verified on 11.6.6 that `not null` with a message, `not null` bare, AND `unique` each produce CE0070 while a plain attribute does not, so the bare form (easy to miss, since the reporter only showed the message form) is flagged too. The CREATE path is the only one that can run this: `ALTER ENTITY … ADD ATTRIBUTE` does not carry the persistence kind, the same limitation MDL020 has. Sweep `mdl-examples/` + `scripts/check-skill-mdl.sh` after adding any error-severity rule — a false positive there breaks every user with that shape. Negative test `mdl-examples/bug-tests/832-npe-validation-rules.fail.mdl` (`.fail.mdl` = must fail check, enforced by `make check-mdl`) plus `-ok.mdl` pinning the other edge; tests `TestValidateEntityNPEValidationRules`. Issue #832", "refs": ["#832"], "ce": ["CE0070"], "rules": ["MDL020", "MDL054"]} -{"area": "mdl/executor", "symptom": "`retrieve $L from Mod.Entity where [Attr = $Var/Mod.Assoc/Attr]` passes `mxcli check` AND `mxcli exec`, then the build fails **CE0161** \"Error(s) in XPath constraint\"", "cause": "Mendix XPath reaches at most ONE hop off a variable, and nothing checked the hop count", "file": "`mdl/executor/validate_microflow.go` (`checkXPathVariableTraversal`, `xpathVarTraversalRe`), called from the `*ast.RetrieveStmt` arm beside MDL047/MDL048", "insight": "Add **MDL055**, error severity, matching a `$var`-rooted path with **2+ segments**. **Establish the boundary against mxbuild first — it is narrower than it looks**: `$Var/Attr` VALID, `$Var/Mod.Assoc` VALID (one hop to the associated object), `$Var/Mod.Assoc/Attr` CE0161. A rule keying on \"a module-qualified segment follows a variable\" would reject the middle form, which is legal; key on hop count instead. Verified on 11.6.6 by building all three and dropping the offender to confirm the other two are clean. **Reject, don't try to serialize** — there is no valid XPath for the two-hop form, so the constraint must be restructured and only the author knows which way. **Verify the suggestion you emit**: both recommended rewrites (`retrieve $Related from $Var/Mod.Assoc;` then constrain on `$Related/Attr`; or invert to `[Mod.Assoc/Mod.Entity = $Var]`) were built and confirmed at 0 errors before the message claimed \"both forms build clean\". Negative test `mdl-examples/bug-tests/831-xpath-variable-traversal.fail.mdl` + `-ok.mdl`; test `TestXPathVariableTraversal`. Issue #831", "refs": ["#831"], "ce": ["CE0161"], "rules": ["MDL047", "MDL048", "MDL055"]} -{"area": "mdl/executor", "symptom": "`mxcli check` reports a microflow rule (e.g. MDL048 `[id = $StringVar]`) but `mxcli exec` writes the same script silently — a script that skips `check` produces a project the build rejects", "cause": "Two different validators. The exec path called `ValidateMicroflowBody` (semantic errors); the MDL0xx rule set lives in `ValidateMicroflow`, which was wired only into `cmd/mxcli/cmd_check.go` and the LSP. Same shape as #836, where a guard existed on every exec path but was never reached from validate", "file": "`mdl/executor/validate.go` (`validateMicroflowRules`, `execEnforcedMicroflowRules`) called from `mdl/executor/cmd_microflows_create.go` — the handler, mirroring `ValidateEntity` in `cmd_entities.go` (NOT `validateWithContext`, which would double-report under `check --references`)", "insight": "Promote an **explicit allowlist** of rules verified against a real mxbuild, never the whole set. **Blanket promotion was tried and reverted**: it turns every error-severity rule into a write barrier, and `MDL009` (\"enumeration splits require exactly one value per branch\") was a **false positive** — a multi-value branch covering every enum value builds at 0 errors on 11.6.6, and the shipped `write-microflows` skill documents that very form (since retired, see the MDL056 row). It also broke an existing repo test whose fixture uses `else` on an enum split. `MDL008` by contrast IS correct (mxbuild: CE0079 per uncovered value + CE0773) — so before promoting any rule, build its construct and read the verdict. Warnings are never promoted. Tests `TestValidateMicroflowRules_ReachedFromExec` and `…_UnverifiedRulesNotPromoted` (the latter fails if the allowlist is widened carelessly). Issue #833", "refs": ["#833", "#836"], "ce": ["CE0079", "CE0773"], "rules": ["MDL008", "MDL009", "MDL048", "MDL056"]} -{"area": "mdl/executor", "symptom": "`mxcli check` errors **MDL009** \"enumeration splits require exactly one value per branch\" on `when Open, Pending then` — but Mendix accepts it, so check rejects valid MDL and contradicts the shipped `write-microflows` skill", "cause": "The rule asserted the opposite of the platform's behaviour. Nobody had built the construct: a multi-value branch covering every value **plus `(empty)`** builds at 0 errors on 11.6.6", "file": "`mdl/executor/validate_microflow.go` (the `*ast.EnumSplitStmt` arm; `checkEnumSplitEmptyBranch`)", "insight": "Retire the assertion and replace it with what actually fails: an enum split needs an outgoing flow per condition value, so a missing branch is **CE0079**. **MDL056** checks the `(empty)` branch — universal, verified to hold even on a `not null` enum attribute, so it needs no enumeration lookup and works from the statement alone. Full value coverage (the other half of CE0079) is deliberately NOT implemented: it needs the enum's member list, i.e. resolving the split variable's type against script or project, which `ValidateMicroflow` cannot see — guessing would trade one false positive for another. **Use a NEW rule ID rather than repurposing**, so anything citing the old number still means the old, wrong thing. `MDL008` (no `else`) is correct and stays — mxbuild gives CE0079 per uncovered value **plus** CE0773 on the else flow. Fix the skill in the same change: it documented the invalid `else` form. Tests `TestValidateMicroflow_EnumSplitMultipleValuesAllowed` / `…RequiresEmptyBranch`; repro `mdl-examples/bug-tests/mdl009-enum-split-empty-branch.fail.mdl` + `-ok.mdl`", "ce": ["CE0079", "CE0773"], "rules": ["MDL008", "MDL009", "MDL056"]} -{"area": "mdl/executor", "symptom": "A published OData service created purely from MDL passes `mxcli check` and then fails the build — `[CE0729] \"The service name should not be empty.\"` and `[CE7375] \"Attribute ID for entity 'X' must be published and be the key when associations are exposed as an associated object id.\"` — the second firing even with no associations exposed at all", "cause": "Two defaults `CREATE ODATA SERVICE` never set. (1) `Name` (the document) and `ServiceName` (the name in the OData metadata document) are different properties and only the first was set; the CONSUMED path had defaulted this for CE0339 all along. (2) `PublishAssociations` defaults to false = \"associations as an associated object id\", which Mendix only allows when the system `ID` is published as the key — but MDL's `expose (Attr (KEY))` publishes an ordinary attribute", "file": "`mdl/executor/cmd_odata.go` (`serviceName` fallback + heal on create-or-modify; `publishAssociationsFor`; `nonPersistablePublishedEntities` warning), `mdl/ast/ast_odata.go` + `mdl/visitor/visitor_odata.go` (`PublishAssociationsSet`)", "insight": "**Wider than reported**: the finding framed CE7375 as a non-persistable-entity problem. Measured on 11.12.1, the identical service with a PERSISTENT entity and a unique key builds 0 errors with `true` and CE7375 with `false` — so the default broke *every* published service, and non-persistable was just where it could not be worked around. Defaulting to true does not pick a preference; it picks the only value that can build from the MDL people write. Needs tri-state (`PublishAssociationsSet`) so an explicit `false` is still honoured, and `create or modify` no longer flips a stored value the author did not mention. **Why nothing caught it**: `mdl-examples/doctype-tests/10-odata-examples.mdl` sets both properties explicitly, so the repo's own example worked around both defaults. Tests `cmd_odata_service_name_test.go`, `cmd_odata_publish_associations_test.go`; examples `f1-10.1-…`, `f1-10.4-…`. mxcli-formula1 #10.1/#10.4", "refs": ["#10"], "ce": ["CE0339", "CE0729", "CE7375"]} -{"area": "mdl/executor", "symptom": "`describe odata service Module.Api` emits MDL that will not parse — `ReadMode: CallMicroflow:Module.Read` matches no value, `expose (Module.Entity.Attr ...)` where the clause takes a bare member name — and quietly renames the entity set, printing the entity TYPE's exposed name in the `as '…'` position where the entity SET's belongs", "cause": "Three independent slips in one emit block. The backend stores a microflow-backed mode as `CallMicroflow:` and a member fully qualified; DESCRIBE printed the stored forms verbatim. The set/type name confusion is invisible in a single-word case and only shows when the two differ (Studio Pro's convention is singular type, plural set)", "file": "`mdl/executor/cmd_odata.go` (`odataModeToMDL`, `bareMemberName`, entity-set exposed name, `KEY` over `IsPartOfKey`)", "insight": "Storage form ≠ input form: anywhere DESCRIBE prints a value read back from the backend, ask whether the *parser* accepts that spelling — `mx check` and the linter never see DESCRIBE output, so nothing else can catch it. Proved by round trip rather than by eye: describe → check (parses) → drop → exec the output → describe again → **byte-identical**, and the rebuilt model reports 0 errors from mxbuild. Tests `cmd_odata_describe_roundtrip_test.go`. mxcli-formula1 #10.5", "refs": ["#10"]} -{"area": "mdl/executor", "symptom": "A `create database connection … type 'Redshift'` (or `'SQLServer'`) executes, builds **0 errors**, and the connection does not work. The skill's own table listed both, and omitted the one value that matters for an unsupported driver", "cause": "mxcli passes the type string straight to BSON (`addStr(e,\"DatabaseType\",…)`) and **mxbuild does not validate it either** — verified on 11.12.1 — so nothing between the author and the runtime says the type is not real. Studio Pro's picker (read from `modeler/ide-client/database-connector-editor/`, identical on 11.10.0/11.12.1/11.13.0) is MSSQL, MySQL, Oracle, PostgreSQL, Snowflake, **BYOD** (\"Other\") — no Redshift, no SQLServer", "file": "`mdl/executor/validate_database_type.go` (`ValidateDatabaseConnectionType`, MDL-DB01), wired in `cmd/mxcli/cmd_check.go`; `.claude/skills/mendix/database-connections.md`", "insight": "**A warning, not an error**: the value set is version-specific and mxcli cannot prove a string wrong on a Mendix version it has not seen — but silence is worse when the build is green and the connection is dead. **`BYOD` is the discovery worth keeping**: it forces connection-string config and *skips the driver-presence check*, so any JDBC driver Mendix has no entry for (DuckDB, SQLite, ClickHouse) works by dropping the JAR in `userlib/`. **Generalisable**: when a doc lists enum values, the shipped Studio Pro editor bundle is the authority — grep `id:\"…\",label:\"…\"` out of `ide-client/`, and diff across cached versions to see whether the set moved. Tests `validate_database_type_test.go`. mxcli-formula1 #6", "refs": ["#6"], "rules": ["MDL-DB01"]} -{"area": "mdl/executor", "symptom": "`DESCRIBE microflow` prints a bare `else` on a `split type` the author never wrote one for, and each describe→exec pass accumulates another", "cause": "An object-type decision always carries an `(empty)` outgoing flow (the null-object case), emitted by the builder whether or not an `else` was written. DESCRIBE rendered that flow as `else`. Invisible until the `InheritanceCase` writer landed — before that every branch degraded to `NoCase`, so nothing distinguished it from a real case", "file": "`mdl/executor/cmd_microflows_show_helpers.go` (the `elseFlow` block in the inheritance-split traversal)", "insight": "Drop the `else` line when its body renders empty — the same `elseLineIdx`/truncate pattern the if/else emitters already use. Exec re-creates the flow, so the omission is lossless and the roundtrip is stable. **Do NOT 'fix' this in the builder**: removing the empty-entity branch there fails the build with **CE0089** \"The '(empty)' value should be configured for an outgoing flow\" — that flow is load-bearing and is why `else` cannot substitute for the base entity's case (CE0090); `(empty)` and the base type cover different things. That wrong fix was implemented first and caught only because every shape was re-run through mxbuild, not because a unit test failed. Tests `TestBuilder_InheritanceSplitKeepsEmptyCaseFlow` (builder must KEEP it) and `TestTraverseFlow_InheritanceSplitOmitsEmptyElse` (describe must not print it)", "ce": ["CE0089", "CE0090"]} -{"area": "mdl/executor", "symptom": "A published service will not build: every whole-number attribute is `[CE5016] \"Attribute … has type Integer, but is published as Edm.Int32\"`, and an exposed enumeration adds CE5016 plus `[CE4583] \"Enumeration 'X' is not published in this service.\"`", "cause": "`mendixAttrTypeToEdm` mapped Integer→Int32 (Mendix publishes it as **Int64**, same as Long), and the enum path wrote `Edm.String` while `EnumerationAsString` was hardcoded `false` — the one combination Mendix rejects, since with the flag false it wants the enumeration published as its own EDM enum type. The function's own comment flagged the unverified rows, and the existing unit test *pinned the wrong answer*", "file": "`mdl/executor/cmd_odata.go` (`mendixAttrTypeToEdm`, `enumPublishedAsString`, `publishedAttrType`), `model/types.go` (`PublishedMember.EnumerationAsString`), `mdl/backend/modelsdk/odata_write.go` + `sdk/mpr/writer_odata.go` (stop hardcoding the flag)", "insight": "**Let mxbuild adjudicate the whole table at once**: publish one attribute of every Mendix type in one service and read the CE5016s off the build. That found Integer (reported) *and* Enumeration (only suspected), and confirmed String/Long/Decimal/Boolean/DateTime were already right — five verified rows for one build. Binary turns out to be unpublishable at all (CE5013), whatever type you give it. **A type and a flag that only work as a pair must travel as a pair** — `Edm.String` is ambiguous between String and a flattened enum, so the flag is the only thing distinguishing them and it belongs on the same struct. Watch for an existing test that encodes the bug: this one asserted `Edm.Int32`, so the fix *failed the suite* until the assertion was corrected. Tests `cmd_contract_test.go`, `cmd_odata_edm_type_test.go`. mxcli-formula1 #16", "refs": ["#16"], "ce": ["CE4583", "CE5013", "CE5016"]} -{"area": "mdl/executor", "symptom": "`create odata client` against a service behind `authentication basic` prints `Warning: could not fetch $metadata: … HTTP 401`, creates the client anyway, and the following `create external entities from …` imports nothing — from a script that reports success", "cause": "The statement's `HttpUsername`/`HttpPassword`/`HEADERS` are stored for the runtime, but the design-time fetch was a bare `client.Get`. The fetch failure is only a warning, so the empty client propagates silently", "file": "`mdl/executor/cmd_odata.go` (`metadataFetchAuth`, `metadataAuthFromStmt`, `fetchODataMetadata`), `mdl/ast/ast_odata.go` + `mdl/visitor/visitor_odata.go` (`HttpUsernameIsLiteral` / `HeaderDef.ValueIsLiteral`)", "insight": "**Only a literal is usable at design time.** The visitor strips a quoted literal's quotes, so `'f1api'` and `Module.ApiUser` both arrive as bare strings — the AST has to record which was written, or mxcli sends a *constant's name* as the password. Unresolved names are reported instead, which is also the honest answer: mxcli has no runtime to resolve a constant against. **A warning on a step something else silently depends on needs to say what breaks next** — the message now names the empty client and the import that will do nothing. Verified against a real basic-auth server that 401s without credentials and 403s without the custom header, so both had to arrive. Tests `cmd_odata_metadata_auth_test.go`. mxcli-formula1 #23", "refs": ["#23"]} -{"area": "mdl/executor", "symptom": "Re-running `create or modify odata service` after editing a `publish entity` block changes nothing — the served `$metadata` is identical, and only `drop odata service` + create picks the edit up", "cause": "The modify branch updated the service's scalar properties and never touched `EntityTypes` / `EntitySets`", "file": "`mdl/executor/cmd_odata.go` (modify branch rebuilds published entities via `astEntityDefToModel`, and carries `AllowedModuleRoles` through)", "insight": "**Replace, don't merge**: a member removed from the script has to leave the service, which merging cannot express — the script is the description of the service. **Carry through what the statement cannot express**: role grants come from a separate `grant access on odata service` and would otherwise be dropped by a modify (reported; *not* reproduced on 11.12.1 — kept as a guard, and the commit says so rather than claiming a fix). Verified: same script yields `Label as 'label'` before and `Label as 'label' (Filterable, Sortable)` after, build stays at 0 errors. Tests `cmd_odata_modify_members_test.go`. mxcli-formula1 #26", "refs": ["#26"]} -{"area": "mdl/executor", "symptom": "A contract property called `name` is generated as `Stg_Drivername` / `Circuitname` — prefixed with the remote type. A page written against the published `$metadata` then fails with `The selected attribute 'F1Live.Drivers.name' no longer exists`, and the *same* field carries a different name in every module because the remote type names differ", "cause": "`attrNameForOData` disambiguates any name in `reservedEntityAttrNames`, and `name` was on that list with the comment \"Mendix system-managed attribute for the object name\". It is not: Mendix builds an external entity with an attribute literally named `name`", "file": "`mdl/executor/cmd_contract.go` (`reservedEntityAttrNames` loses one entry; the import now reports the renames it does make)", "insight": "**Test the whole list at once, not the reported entry.** One contract with a property per listed name, prefixing disabled, then `mx check`: CE7247 \"The name 'x' is a reserved word\" for `id`/`owner`/`changedBy`/`changedDate`/`createdDate`/`type`/`context`, and silence for `name`. That turns \"is the list wrong?\" into \"which rows are wrong?\" for the cost of a single build, and it *earns* the seven entries that stay rather than leaving them as folklore. Two existing tests pinned the old behaviour and had to be corrected — a hand-maintained list of platform rules will accrete guesses unless each row can point at an error code. **Migration**: a re-import renames the attribute back, so references to the prefixed name must follow. Tests `cmd_contract_reserved_test.go`. mxcli-formula1 #28", "refs": ["#28"], "ce": ["CE7247"]} -{"area": "mdl/executor", "symptom": "`create odata client` with credentials given as constants (`HttpUsername: '@Module.ApiUser'`) still gets HTTP 401 and an empty client, after the fix that made literal credentials work. Sharpened by the same release making a constant `ServiceUrl` mandatory — the shape the tool insists on is the shape whose credentials it will not read", "cause": "`resolveCredential` trusted the visitor's isLiteral flag. `'@Module.ApiUser'` **is** a STRING_LITERAL, so the flag said \"literal\" and the previous code sent the fifteen characters `@Module.ApiUser` as the username — and the unresolved-credential note did not fire either, because as far as the code knew nothing was unresolved", "file": "`mdl/executor/cmd_odata.go` (`resolveCredential`, `constantReference`, `designTimeConstants`)", "insight": "**A syntactic classification is not a semantic one.** The visitor can say \"this was a quoted string\"; only the executor can say \"this quoted string names a constant\". Any flag of the form isLiteral needs the consumer to ask what the literal *contains* before treating it as a value. **The fix is to resolve, not to refuse**: a constant's design-time default is exactly what Studio Pro sends on the same fetch, so reading it is the value rather than a workaround — and mxcli already has the project open. Three spellings must all work (`'v'`, `@M.C`, `'@M.C'`); a dotted literal like a password containing a dot must not be mistaken for a reference. Tests `cmd_odata_metadata_auth_test.go`. mxcli-formula1 #23 follow-up", "refs": ["#23"]} -{"area": "mdl/executor", "symptom": "A `MOVE` cannot be confirmed and a module's layout cannot be reviewed: `SHOW STRUCTURE` groups by document type at every depth and never names a folder, `DESCRIBE` answers for one document at a time, so checking where things ended up means opening the `.mpr` as SQLite", "cause": "The read side of folders was simply never built. `MOVE`/`DROP FOLDER` write containment; nothing read it back", "file": "New `mdl/executor/cmd_list_folders.go` + grammar (`FOLDERS` lexer token, `showOrList FOLDERS (IN …)?` in `MDLCatalog.g4`, `FOLDERS` added to the `keyword` rule so it stays usable as an identifier), `mdl/ast/ast_query.go`, `mdl/visitor/visitor_query.go`, `mdl/executor/executor_query.go`", "insight": "**A layout listing must show what is *not* there**: empty folders (`[0]`) and documents still at the module root, or it cannot be diffed against an intended layout — that is the whole use. Documents are indexed by `ContainerID` across every list call the backend offers, each best-effort, so a backend that cannot answer one kind yields a listing missing that kind rather than no listing. **Do not stub the hierarchy in the test** — `mkHierarchy` populates `moduleNames` but not `folderNames`, so `BuildFolderPath` returns `\"\"` and every folder silently collapses into the module root, which looks exactly like the bug. Build it from the mock's `ListModules`/`ListUnits`/`ListFolders`, as `getHierarchy` does. Tests `cmd_list_folders_test.go`, example in `18-folder-examples.mdl`. mxcli-formula1 issue #2", "refs": ["#2"]} -{"area": "mdl/executor", "symptom": "`DESCRIBE PAGE` reports a drill-down button as `linkbutton b (Caption: 'Weekend', Action: show_page Mod.Page)` — the `(Race: $currentObject)` argument is gone. The model is fine (`mx check` is clean, and an unmapped required page parameter is a consistency error, so it could not have built), but the description reads as a diagnosis mid-hunt and costs cycles fixing a button that was correct", "cause": "mxcli deliberately writes `ParameterMappings` as an empty array for a page action, because Studio Pro infers the row object from the enclosing widget and rejects an explicit `$currentObject` Argument as CE0115 (#296). That decision was right; its other half was missing. `renderClientActionMDL` read only explicit mappings, and the writer's comment *asserted* DESCRIBE recovered the implicit one — it never did", "file": "`mdl/executor/cmd_pages_describe_output.go` (`pageActionParameters`, `targetPageParameterNames`), stale comment corrected in `sdk/mpr/writer_widgets_action.go`", "insight": "**When a writer stores something implicitly, the reader owes it an explicit reconstruction — and a comment claiming the reader already does is worth nothing until a test says so.** Recover from the *target page's* declared parameters, not from the action, since that is where the information actually lives. Guard both ends: an explicit mapping still wins (recovery fills a gap, it does not override Studio Pro), and an unresolvable page yields no arguments rather than invented ones — a description that omits an argument is recoverable, one that names a parameter that does not exist is not. **A lossy DESCRIBE is costliest exactly when it is most used**, because DESCRIBE is what you reach for once you have stopped trusting the model. Tests `cmd_pages_describe_pageparams_test.go`; the control (explicit-only) reproduces the reported output verbatim. mxcli-formula1 §39", "refs": ["#296"], "ce": ["CE0115"]} -{"area": "mdl/executor", "symptom": "A published OData resource backed by a read microflow silently returns the wrong thing: `?$top=5` yields the whole collection with a 200, and a client re-reading a held row by key gets the collection default and adopts the FIRST row as that object's identity. No error anywhere — well-formed request, valid collection, correct `$count`, 200", "cause": "Two promises the service makes on the microflow's behalf and nothing checked: the `KEY` in `expose (…)`, and the `TopSupported`/`SkipSupported` annotations (which default to **true** when unspecified). Mendix applies no query options to a read-microflow resource — it hands over the request and returns what comes back", "file": "New `mdl/executor/validate_odata_read_contract.go` (MDL-ODATA02 key promise, MDL-ODATA03 capability over-claim), wired in `cmd/mxcli/cmd_check.go`; the response contract documented in `.claude/skills/mendix/odata-data-sharing.md`", "insight": "**A read microflow cannot answer 400** — unlike an OData action or an insert/update/delete microflow, the read capability has no `System.HttpResponse` parameter ([docs](https://docs.mendix.com/refguide/published-odata-entity/#custom-http-response)). Its contract is therefore *declarative*: declaring `TopSupported: No` is the read path's only substitute for the refusal it cannot send. **Pick a trigger you can prove**: both rules fire only when the microflow takes no `System.HttpRequest` parameter, because then it provably cannot see a key or a query option; a microflow that does take it gets the benefit of the doubt, since proving *which* options it parses needs real analysis and a rule that guesses gets switched off. **Verify a check rule against a real parse, never a hand-built AST** — the visitor stores `ReadMode` as `MICROFLOW Module.Name` **upper-cased**, so a case-sensitive prefix match made the whole rule dead while it still looked right; the casing is now pinned by its own test. Tests `validate_odata_read_contract_test.go`, examples in `10-odata-examples.mdl` (both correct shapes: request-aware, and honestly declared). mxcli-formula1 §37/§20, suggested issues 2 and 3"} -{"area": "mdl/executor", "symptom": "A published OData entity with no `KEY` fails the build with **CE6585** \"Published entity 'X' must have a key defined.\" — so any advice of the form \"drop the KEY\" is impossible to follow", "cause": "Mendix requires every published entity to have a key. MDL-ODATA02's suggestion offered \"…or drop the KEY\" as the alternative to answering a key lookup, and a doctype example demonstrated that non-existent option", "file": "`mdl/executor/validate_odata_read_contract.go` (suggestion text), `mdl-examples/doctype-tests/10-odata-examples.mdl`, `.claude/skills/mendix/odata-data-sharing.md`", "insight": "**Query options you may decline; the key you may not.** A microflow-backed resource whose rows a client can hold *must* answer the key lookup — there is no opt-out, which makes MDL-ODATA02's real remedy singular rather than a choice. **Verify the remedy a diagnostic recommends, not just the diagnosis** — the rule correctly identified an unanswerable KEY and then proposed something mxbuild rejects, which is worse than saying nothing. This is the second time in one session that a doctype example was validated with `mxcli check` (parse-only) instead of the integration gate; `mxcli check` cannot see CE-codes at all, so **any change to `mdl-examples/doctype-tests/` needs `go test -tags integration -run TestMxCheck_DoctypeScripts/