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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-10-26", "symptom": "A member that reuses a name already on the entity's generalization chain passed `check -p` and exec, and mxbuild rejected it as CE0069 \"Duplicate member name 'X'.\" — an attribute on a specialization named like an inherited attribute (`HR.Emp extends Other.Person (Name: …)`), a cross-module association `HR.Person_Dept from HR.Emp` beside the inherited `Other.Person_Dept`, `extends System.User (Name: …)` / `(UserRoles: …)`, `alter entity <generalization> add attribute` of a name a specialization has, a rename into such a name, and an entity's own attribute and association of one name", "cause": "Nothing decided 'this entity already has a member of that name' along the chain. The only CE0069 guard was dropInheritedTargets inside bulk ALTER ENTITIES, which drops a child when its parent is also a target and does not look outside the target set; create entity, alter entity add/rename attribute, create association, rename association and `set owner both` checked only the entity's own attribute list or the module's association names", "file": "`mdl/executor/member_name_clash.go` (memberWorld, memberNameClash — the one verdict; refuseMemberNameClash in execCreateEntity, execAlterEntity add/rename, execAlterEntities before any write, execCreateAssociation, execAlterAssociation set owner both, execRenameAssociation; validateMemberNameClashes in the check -p reference pass; scriptMemberNameClashes as MDL-MEMBER01 in project-free ValidateProgram)", "insight": "Measure the rule's edges before writing it, in one app with no other error — 17 shapes in a single mx check run on 11.13.0 settled it: CE0069 covers attribute/attribute, association/association, attribute/association BOTH ways, two levels up, System ancestors' attributes AND associations (System.User.Name, System.UserRoles, System.FileDocument.Size), the DOWNWARD direction (generalization gains a specialization's member), renames, and an entity's own attribute vs own association. An association is a member of its FROM entity only — of its TO entity too under owner Both (reference and reference set); an attribute on the TO end of an owner-Default association does not collide. Names differing only in case do NOT collide, so the comparison is exact, unlike validateBusinessEventInheritedMembers. Check must replay the script in order over the project's members (create, alter add/drop/rename, create/drop/rename association, rename/move entity): judging against the stored model alone refuses a drop-then-re-add and misses the ordinary one-script case, and a child created before its lazily-resolved parent is only caught by also walking DOWN from the parent when it is created. A refusal reports only clashes the statement introduces (a re-run `create or modify` of an unchanged entity is not judged), and the bulk path is judged for every target before the first write so a refusal leaves nothing behind", "refs": ["ako/mxcli#1645"], "ce": ["CE0069"], "rules": ["MDL-MEMBER01"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-10-26", "symptom": "A page parameter named `Context` (`Params: ( $Context: MyFirstModule.Sale )`) passed `check` and exec, and mx check reported CE7247 \"The name 'Context' is a reserved word.\" at Page parameter 'Context' — after the page and its callers were written. The same for a snippet, Java action or JavaScript action parameter named with any platform-reserved word (`Type`, `Id`, `Owner`, `CurrentUser`, a Java keyword such as `Class`), and for anything named `Object`", "cause": "The platform reserved-word list (types.PlatformReservedWords) was applied to attribute names (MDL021) and enumeration values (MDL010) only; no validator looked at parameter names. The list also lacked `object`, which is CE7247 everywhere the list applies", "file": "`mdl/executor/validate_reserved_parameter_names.go` (validateReservedParameterNames, MDL-PARAM01, wired into ValidateProgram; refuseReservedParameterNames in execCreatePageV3, execCreateSnippetV3, execAlterPage, execCreateJavaAction, execCreateJavaScriptAction), `mdl/types/reserved_words.go` (+`object`)", "insight": "Measure which list applies WHERE, before reusing one: ~120 names × 6 contexts in three mx check runs on 11.13.0. Pages, snippets, Java actions and JavaScript actions share the attribute list exactly (case-insensitive, quoted refused) — every listed name tried was CE7247, every unlisted one clean, except `Object`, which nobody had listed because it is not a Java keyword (also CE7247 on an attribute, an enumeration value and an ENTITY). Microflow and nanoflow parameters and variables do NOT use it — `$Context`, `$Type`, `$Class` build clean there — and are refused for a different set: the system variables (CE0110 \"Variable name 'latestError' is reserved.\" for currentObject/latestError/latestHttpResponse/latestSoapFault/currentSession/currentDeviceType, parameters and variables) and CE0039 for a variable named CurrentUser — not covered by this fix. Page VARIABLES named Context/Type/Object build clean too. Extending the shared list rather than adding a parallel one made MDL021/MDL010 pick up `Object` for free", "refs": ["ako/mxcli#1651", "mendixlabs/mxcli#1642"], "ce": ["CE7247"], "rules": ["MDL-PARAM01", "MDL021", "MDL010"]}
26 changes: 17 additions & 9 deletions .claude/skills/mendix/create-page/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ description: "CREATE PAGE syntax reference — parameters, variables, layouts, a
|---|---|
| 28-43 | Reference files |
| 44-46 | Overview |
| 47-198 | Syntax |
| 199-269 | Basic Examples |
| 270-291 | Modifying Existing Pages |
| 292-395 | Conditional Visibility and Editability |
| 396-560 | Known Limitations |
| 561-569 | Tips |
| 570-596 | Related Commands |
| 597-621 | PLUGGABLEWIDGET Escape Hatch |
| 622-625 | See Also |
| 47-206 | Syntax |
| 207-277 | Basic Examples |
| 278-299 | Modifying Existing Pages |
| 300-403 | Conditional Visibility and Editability |
| 404-568 | Known Limitations |
| 569-577 | Tips |
| 578-604 | Related Commands |
| 605-629 | PLUGGABLEWIDGET Escape Hatch |
| 630-633 | See Also |

<!-- /skill-index -->
# CREATE PAGE - MDL Syntax Guide
Expand Down Expand Up @@ -63,6 +63,14 @@ create [or replace] page Module.PageName
}
```

**Parameter names are held to the platform's reserved words.** A page or snippet
parameter named `Context`, `Type`, `Id`, `Owner`, `Object`, `CurrentUser` or a Java
keyword (`Class`, `New`, …), in any case, is CE7247 "The name 'Context' is a reserved
word." at build time — and quoting does not help. `mxcli check` refuses it (MDL-PARAM01).
Name the parameter after its entity (`$Sale`) or qualify it (`$LaneContext`). The
system variable names (`$currentObject`, `$latestError`, …) are *not* on this list for
pages.

**Pop-up dimensions** (`PopupWidth` / `PopupHeight` / `PopupResizable`) apply when the
page is opened in a pop-up. They are optional — omitting them uses the Mendix defaults
(600 × 600, not resizable). Unlike the other header keywords, these property names are
Expand Down
26 changes: 24 additions & 2 deletions .claude/skills/mendix/generate-domain-model/reference/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,27 @@ access is out of date". The one exception is entities extending `System.User`, w
inherited platform members Mendix manages and which must not be granted. See
`manage-security`.

**Member names are unique along the chain.** An inherited member *is* a member of
the child, so the child cannot declare one of the same name — not an attribute, and
not an association it owns (the association's name is a member of its FROM entity,
and under `owner both` of its TO entity too). That holds up the chain to the System
entities (`System.User` has `Name` and the association `UserRoles`;
`System.FileDocument` has `Name` and `Size`) and down it: a generalization cannot
gain a member a specialization already declares. An entity's own attribute and
association cannot share a name either. Names that differ only in case do not
collide. `mxcli check` refuses each (MDL-MEMBER01); Mendix reports CE0069
"Duplicate member name".

```sql
mdl 1;
-- ❌ WRONG: Employee inherits Name from Person — CE0069 "Duplicate member name 'Name'."
create persistent entity Module.Person (Name: string(100));
create persistent entity Module.Employee extends Module.Person (Name: string(100));
```

Drop the child's copy and use the inherited one, or give it another name
(`DisplayName`).

#### System Attributes (Auditing)

Mendix supports four built-in auditing properties on persistent entities. Declare them as regular attributes using pseudo-types (like `autonumber`):
Expand Down Expand Up @@ -688,8 +709,9 @@ create persistent entity Module."VATRate" (
> quoted** (the check strips the quotes and still flags them): `Type` (CE7247, MDL021),
> the audit attributes `CreatedDate` / `ChangedDate` / `Owner` / `ChangedBy` (MDL020 — use
> the `AutoCreatedDate` / `AutoChangedDate` / `AutoOwner` / `AutoChangedBy` pseudo-types
> instead), plus `ID`, `GUID`, `CurrentUser` and the Java-keyword list. `"Type": String`
> fails MDL021 — rename to `ResourceType` / `TypeValue`.
> instead), plus `ID`, `GUID`, `CurrentUser`, `Object` and the Java-keyword list. `"Type": String`
> fails MDL021 — rename to `ResourceType` / `TypeValue`. The same list applies to page,
> snippet, Java action and JavaScript action **parameters** (MDL-PARAM01).

> **Caveat 2 — a third grammar: OQL, where quoting DOES work.** `Year`, `Month`,
> `Quarter`, `Week`, `Day`, `Hour`, `Minute`, `Second`, `Weekday`, `DayOfYear`,
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,10 @@ When generating Mendix expression strings (e.g., in `expressionToString()`), sin

The skills advise **quoting all identifiers** to avoid keyword collisions, but this only escapes **MDL parser** keywords (so `"create"`, `"status"`, `"end"` become valid attribute names). It does **not** exempt names the Mendix **platform** reserves for entity members — those are rejected by Studio Pro (and by `mxcli check --references`) **even when quoted**, because the check strips the quotes and validates the bare name:

- `Type` → CE7247 / `MDL021` ("reserved word"). Rename (e.g. `ResourceType`, `TypeValue`). Also `ID`, `GUID`, `CurrentUser`, and the Java-keyword list.
- `Type` → CE7247 / `MDL021` ("reserved word"). Rename (e.g. `ResourceType`, `TypeValue`). Also `ID`, `GUID`, `CurrentUser`, `Object`, and the Java-keyword list; page/snippet/Java/JavaScript action parameters too (`MDL-PARAM01`).
- `CreatedDate` / `ChangedDate` / `Owner` / `ChangedBy` → `MDL020` on persistent entities. Use the `AutoCreatedDate` / `AutoChangedDate` / `AutoOwner` / `AutoChangedBy` pseudo-types for the audit fields, or a different name for an unrelated value.

The reserved-word lists live in `mdl/executor/cmd_enumerations.go` (`mendixReservedWords`, `mendixSystemAttributeNames`). "Always safe to quote" in the skills means *parser*-safe, not *platform*-safe.
The reserved-word lists live in `mdl/types/reserved_words.go`. "Always safe to quote" in the skills means *parser*-safe, not *platform*-safe.

### A `GUID` Is the Database's Identity — Never Mint One for an Existing Element

Expand Down
1 change: 1 addition & 0 deletions docs-site/src/reference/domain-model/create-entity.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ INDEX (OrderDate DESC);

- `String` requires an explicit length: `String(200)`. Use `String(unlimited)` for unbounded text.
- `EXTENDS` must appear **before** the opening parenthesis, not after the closing one.
- A member name must be unique along the generalization chain: a specialization cannot declare an attribute or own an association named like a member it inherits (from `System.User` and the other System entities too), and a generalization cannot gain one a specialization already has. An entity's attributes and associations share that name space; names differing only in case do not collide. `mxcli check` refuses it (MDL-MEMBER01); Mendix reports it as CE0069 "Duplicate member name".
- Boolean attributes without an explicit `DEFAULT` automatically default to `FALSE`.
- `AutoNumber` attributes are only valid on persistent entities.
- The `@Position` annotation is optional and only affects the visual layout in Mendix Studio Pro.
Expand Down
2 changes: 1 addition & 1 deletion docs-site/src/reference/page/create-page.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ A replace (`OR REPLACE` or `OR MODIFY`) is refused when the stored page holds a

The page declaration block `( ... )` contains comma-separated key-value properties:

- **Params** -- Page parameters. Each parameter has a `$`-prefixed name and a type — either an entity (`Module.Entity`) or a primitive (`String`, `Integer`, `Decimal`, `Boolean`, `DateTime`). Parameters are passed when the page is opened (e.g., from a microflow `SHOW PAGE` or from a data view). Primitive parameters require Mendix 11.6+.
- **Params** -- Page parameters. Each parameter has a `$`-prefixed name and a type — either an entity (`Module.Entity`) or a primitive (`String`, `Integer`, `Decimal`, `Boolean`, `DateTime`). Parameters are passed when the page is opened (e.g., from a microflow `SHOW PAGE` or from a data view). Primitive parameters require Mendix 11.6+. A parameter cannot be named with a platform-reserved word — `Context`, `Type`, `Id`, `Owner`, `Object`, `CurrentUser` or a Java keyword, in any case and even quoted: Mendix reports CE7247 "The name 'Context' is a reserved word.", and `mxcli check` refuses it (MDL-PARAM01). The same holds for snippet, Java action and JavaScript action parameters.
- **Title** -- The page title displayed in the browser tab or page header.
- **Layout** -- The layout the page is based on. Must be a qualified name referring to an existing layout (e.g., `Atlas_Core.Atlas_Default`, `Atlas_Core.PopupLayout`).
- **Folder** -- Optional folder path within the module. Nested folders use `/` separator.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- NEGATIVE TEST (.fail.mdl) — EXPECTED to fail `mxcli check` with MDL-MEMBER01
-- (ako/mxcli#1645).
--
-- An association is a member of its FROM entity (and, under owner Both, of its
-- TO entity too), so its name shares the entity's member name space. HR.Emp
-- inherits the association Person_Dept from Other.Person; a second association
-- of that name from HR.Emp passed check and exec, and mxbuild rejected it with
-- [CE0069] "Duplicate member name 'Person_Dept'." at Cross-module association 'HR.Person_Dept'
-- Measured on mx 11.13.0 (and reported on 11.12.5).
mdl 1;
create module Other;
create module HR;
create persistent entity Other.Person (Name: String(50));
create persistent entity Other.Dept (Name: String(50));
create association Other.Person_Dept from Other.Person to Other.Dept;
create persistent entity HR.Emp extends Other.Person (Code: String(10));
create association HR.Person_Dept from HR.Emp to Other.Dept;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- NEGATIVE TEST (.fail.mdl) — EXPECTED to fail `mxcli check` with MDL-MEMBER01
-- (ako/mxcli#1645).
--
-- A member name must be unique along an entity's generalization chain. HR.Emp
-- inherits Name from Other.Person, so declaring its own Name is a second member
-- of that name: check and exec passed it, and mxbuild rejected the model with
-- [CE0069] "Duplicate member name 'Name'." at Attribute 'HR.Emp.Name'
-- Measured on mx 11.13.0 (and reported on 11.12.5).
--
-- The same holds for an association (see member-name-1645-inherited-association),
-- for a System ancestor's members (`extends System.User` with `Name` or
-- `UserRoles`), for a generalization gaining a member a specialization already
-- has, and for a rename into such a name. Names that differ only in case do not
-- collide.
mdl 1;
create module Other;
create module HR;
create persistent entity Other.Person (Name: String(50));
create persistent entity HR.Emp extends Other.Person (Name: String(50), Code: String(10));
24 changes: 24 additions & 0 deletions mdl-examples/bug-tests/page-params-1651-reserved-context.fail.mdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-- NEGATIVE TEST (.fail.mdl) — EXPECTED to fail `mxcli check` with MDL-PARAM01
-- (ako/mxcli#1651, a mirror of mendixlabs/mxcli#1642).
--
-- `Context` is on the platform's reserved-word list — the one attribute names
-- (MDL021) and enumeration values (MDL010) are checked against — and that list
-- binds page parameters too. check and exec passed this page, and mx check
-- then reported
-- [CE7247] "The name 'Context' is a reserved word." at Page parameter 'Context'
-- Measured on mx 11.13.0 (and reported on 11.12.5): the same for a snippet, Java
-- action and JavaScript action parameter, in any case, and quoted. Microflow and
-- nanoflow parameters are judged against a different list and build clean.
--
-- The reporter's workaround, `$LaneContext`, builds clean; so does `$Sale`.
mdl 1;
create persistent entity MyFirstModule.Sale (Name: String(100));
create page MyFirstModule.Sale_View (
Title: 'Sale',
Layout: Atlas_Core.Atlas_Default,
Params: ( $Context: MyFirstModule.Sale )
) {
layoutgrid lg { row { column (DesktopWidth: AutoFill) {
dataview dvSale (DataSource: $Context) { textbox tbName (Attribute: Name) }
} } }
};
5 changes: 5 additions & 0 deletions mdl/executor/cmd_alter_page.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ func execAlterPage(ctx *ExecContext, s *ast.AlterPageStmt) error {
if !ctx.ConnectedForWrite() {
return mdlerrors.NewNotConnectedWrite()
}
// A parameter named with a platform-reserved word is CE7247 (MDL-PARAM01,
// ako/mxcli#1651) — refused here too, so --no-check cannot write it.
if err := refuseReservedParameterNames(s); err != nil {
return err
}

h, err := getHierarchy(ctx)
if err != nil {
Expand Down
13 changes: 13 additions & 0 deletions mdl/executor/cmd_associations.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ func execCreateAssociation(ctx *ExecContext, s *ast.CreateAssociationStmt) error
return mdlerrors.NewValidationf("%s\n %s", vs[0].Message, vs[0].Suggestion)
}

// The association's name becomes a member of its FROM entity (and of its
// TO entity under owner Both), which must be unique along that entity's
// generalization chain — CE0069 (ako/mxcli#1645). Before the module is
// auto-created, so a refusal leaves nothing behind.
if err := refuseMemberNameClash(ctx, s); err != nil {
return err
}

// Find or auto-create module
module, err := findOrCreateModule(ctx, s.Name.Module)
if err != nil {
Expand Down Expand Up @@ -321,6 +329,11 @@ func execAlterAssociation(ctx *ExecContext, s *ast.AlterAssociationStmt) error {
return mdlerrors.NewValidationf("%s\n %s", v.Message, v.Suggestion)
}
}
// Owner Both makes it a member of the TO entity too (CE0069,
// ako/mxcli#1645).
if err := refuseMemberNameClash(ctx, s); err != nil {
return err
}
}

// Try intra-module associations first
Expand Down
16 changes: 16 additions & 0 deletions mdl/executor/cmd_entities.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ func execCreateEntity(ctx *ExecContext, s *ast.CreateEntityStmt) error {
}
}

// A member name must be unique along the generalization chain — up to the
// root, System entities included, and down through the specializations
// (CE0069, ako/mxcli#1645). The verdict check gives; before the module is
// auto-created, so a refusal leaves nothing behind.
if err := refuseMemberNameClash(ctx, s); err != nil {
return err
}

// Find or auto-create module
module, err := findOrCreateModule(ctx, s.Name.Module)
if err != nil {
Expand Down Expand Up @@ -1305,6 +1313,10 @@ func execAlterEntity(ctx *ExecContext, s *ast.AlterEntityStmt) error {
if isViewEntity(entity) {
return viewEntityAttributeSetRefusal(s.Name.String(), "add", a.Name)
}
// Not a name another member on the chain has (CE0069, ako/mxcli#1645).
if err := refuseMemberNameClash(ctx, s); err != nil {
return err
}
// A specialization cannot store a system member (see
// checkSpecializationSystemMembers): setting the flag here would be
// dropped by the writer, which stores none on a Generalization.
Expand Down Expand Up @@ -1437,6 +1449,10 @@ func execAlterEntity(ctx *ExecContext, s *ast.AlterEntityStmt) error {
if target == nil {
return mdlerrors.NewNotFoundMsg("attribute", s.AttributeName, fmt.Sprintf("attribute '%s' not found on entity %s", s.AttributeName, s.Name))
}
// Nor a rename into one (CE0069, ako/mxcli#1645).
if err := refuseMemberNameClash(ctx, s); err != nil {
return err
}
target.Name = s.NewName
// The entity's own access and validation rules hold the attribute's
// qualified name as a string, so they have to move with it *in the model*
Expand Down
Loading
Loading