Skip to content

Sync ako/mxcli: marketplace package and search --exclude-marketplace (#1383, #1493); MCP alter page in folders (#1593) - #1607

Merged
ako merged 78 commits into
mendixlabs:mainfrom
ako:main
Oct 21, 2026
Merged

ako merged 78 commits into
mendixlabs:mainfrom
ako:main

Conversation

@ako

@ako ako commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator

Syncs ako/mxcli:main into upstream: 8 commits from fork PRs ako#1501 and ako#1517.

Integrated contributor PRs (ako#1517)

Both features are approved. The contributor's commits (@stagknee) are kept unchanged, so GitHub should mark the PRs as merged when this lands.

PR Change Merge notes
#1493 mxcli search --exclude-marketplace leaves out hits in Marketplace modules. It is off by default (#1477). The MDL SEARCH statement is unchanged. clean
#1383 mxcli marketplace package <Module> exports a module as a .mpk through mx create-module-package (#1382) CHANGELOG only

What #1383 adds over calling mx directly:

  • It runs mx on a temporary copy, so the project isn't changed. mx writes theme-cache/, deployment/sass/ and the module's themesource/ into whatever project it's given.
  • It refuses an mx of a different Mendix version than the project's. --mxbuild-path overrides this, with a warning.
  • It opens the finished package and warns about resources/, javasource/ or themesource/ files that belong to other modules, and prints the --exclude pattern that drops them.
  • It writes <Module>.mpk into place in one step, maps mx exit codes to readable messages, and has --json output.

Follow-ups on top:

MCP backend: documents in folders (#1593, ako#1501)

  • alter page over the MCP backend now works for a page inside a folder. It used to fail with module not found: <uuid>, because it looked up the page's container, which is the folder, as a module.
  • It now uses the backend's folder-aware resolver. Four sibling paths had the same bug and are fixed the same way:
    • create or modify of a constant in a folder
    • create or modify of a workflow in a folder
    • DROP of a constant or enumeration in a folder that was created in the same session
  • Tests:
    • They cover the module root as the control, one folder, nested folders and documents created in the same session.
    • An end-to-end run uses a fake Studio Pro server.
    • With the fix stashed, every case except the control fails with the reported symptom.
    • Not run against a live Studio Pro.
  • Docs: a bug-test script, a finding, and a paragraph in docs-wiki/bug-patterns/duplicate-resolver-drift.md.

Validation

  • go build and go vet (Linux and Windows) are clean.
  • CI runs the tests.
  • The contributor PR heads are unchanged since their security review

stagknee and others added 30 commits October 15, 2026 12:19
…es (#1477)

An audit of the app's own text reported a vendor module's text beside the
project's. The flag drops hits whose module was downloaded from the
Marketplace, a module's own documentation included. It is off by default and
the SEARCH statement is unchanged. Which modules are Marketplace modules is
read from the current snapshot only, so a module that was one in an older
snapshot is not left out of every later search.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… a list

The hand-written list of document types would drift as the objects view
grows; "every element the catalog records" stays true. Suggested in review
of #1478.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
)

Wraps `mx create-module-package` (-l by default, --all-libs to bundle every JAR),
the reverse of `marketplace install --file`. Warns on stderr about other modules'
resources/javasource/themesource files in the package, with the plain-dot --exclude
that drops them. mx writes into the project (measured), so it runs on a temp copy.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… review follow-ups (#1383)

ResolveMxForVersion prefers an mx on PATH and falls back to any cached one, so
the package could silently be made by another Mendix version than its app.
mxcli now reads the version of the mx it found (mx.deps.json beside it, else
its version-named folder) and refuses a different major.minor.patch, or an mx
whose version it cannot tell, naming both versions and the fix. --mxbuild-path
is the override: a mismatch then only warns on stderr; a path that does not
exist is an error rather than a quiet fall back to PATH.

Also: mx's output on a successful export goes to stderr; the context reaches
mx (exec.CommandContext); the printed --exclude is quoted for a POSIX shell;
an output directory inside the project is not copied into the temporary copy;
the unused inspectPackage is dropped.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ALTER PAGE over the MCP backend failed for every page inside a folder:

  failed to open page for mutation: module not found: <uuid>

The uuid was the page's parent folder. resolvePageUnit passed
page.ContainerID straight to GetModule, which only holds for a page at the
module root. The backend already had the folder-aware answer,
moduleNameForContainer; the page mutator now uses it, for saved pages and
for pages created this session.

The same direct lookup sat in UpdateConstant and UpdateWorkflow (CREATE OR
MODIFY on a foldered constant or workflow failed the same way) and in
forgetSessionConstant / forgetSessionEnum (DROP of a foldered document left
it registered). Those now resolve through the same helper.

Control: with the production change stashed, page_mutator_folder_test.go
fails with "OpenPageForMutation: module not found: fPages" plus the three
sibling failures; the module-root case passes either way.

Fixes mendixlabs#1593

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZCjbSMETkeDrr8bHi6LCD
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZCjbSMETkeDrr8bHi6LCD
…ions (mendixlabs#1498)

Point 2 of the 2026-10-16 follow-up on #1498: `check -p --references`
reports CE5400 for an `export level api` microflow exposing a Hidden
entity of a Protected (add-on) module, but MDL had no way to make the
entity Usable. `create persistent entity Shop.Order (...) export level api`
failed with "extraneous input 'export' expecting the start of a statement"
(reproduced on main), and attributes, associations and enumerations had
no clause either. check also missed CE5400 for a Hidden enumeration.

The microflow's `export level api|hidden` clause now goes on create
entity / view entity / each attribute / create association / create
enumeration, and `set export level` on alter entity / association /
enumeration and modify attribute. An absent clause keeps the stored
level. The converters write the semantic level (Hidden when unset), and
the #801/#816 carries stand down for an authored one. Reads fill
Attribute / Association / CrossModuleAssociation / Enumeration
ExportLevel, describe prints `export level api` (Hidden omitted), and
the clause is gated at 9.3.0 (DomainModels exportLevel and
Projects Document.exportLevel, mendixmodelsdk 4.116.0). The
Protected-module check takes a level stated anywhere in the script over
the stored one, checks enumerations for CE5400, and its hints name the
alter statement. The MCP backend refuses a level PED would drop.

Measured on blank 11.14.0 apps with the module set to Protected:
- API microflows exposing a Hidden entity and enumeration: CE5400 x2;
  after `alter ... set export level api`: 0 errors.
- Another module changing a Hidden attribute and retrieving over a
  Hidden association: CE1406 x2; with `export level api` on both: 0.
- The bug-test file: 0 errors; with its clauses stripped: CE1406 x4.
- Ordinary module with API levels: 0 errors.
- describe -> exec round-trips all four, `create or modify` without the
  clause keeps API, and the alter forms set Hidden back.

Controls: with the backend changes reverted, the write tests store
"Hidden" for an authored API (the reported symptom). With the validator
and describe reverted, their tests fail. With the visitor's entity
clause stubbed, the parse test reads nil.

Not covered: check does not yet predict CE1406 for a Hidden attribute
or association used from another module (it judges entity, microflow
and page targets).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxAn3VqxUnZ7ZGUnzAYpbT
…lements by rule, not by a list

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
…her Mendix version, and review follow-ups (#1383)

CHANGELOG.md conflict resolved by keeping both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
fix(mcp): resolve a foldered document's module through its folder chain
…e to see the list

mendixlabs#1493 replaced the hand-kept list of document types in
`search --help` with "every element the catalog records", which claims
too much: role mappings, references and other catalog rows carry no
documentation. It now reads "every element that has documentation" and
names the commands that show the full list (the catalog tables with a
Description or Documentation column).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
mendixlabs#1383 added the package section and moved the ones after
it. Regenerated with scripts/skill-index.py (make build).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
Integrate wave 3 of the upstream PRs (mendixlabs/mxcli #1493 #1383), with review follow-ups.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiMsnXa9crge2qnSk9S7Zn
A `@curve` on a statement inside a `loop` or `while` body passed check and
exec and was not stored; `describe` printed the body without it, while the
same annotation after the loop was kept.

The body is built by its own flowBuilder, which recorded the curve, and the
loop hoisted that builder's flows to the parent without the curves that
belong on them. applyFlowCurves runs once, on the builder that owns the
flows, so the body's curves were never applied. The error-handler builder
had the same merge (#991); it is now one helper, adoptCurves, which the
loop, while and error-handler merges all call.

Refs #1507

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HjYj5GTj2in3ckFYaDBAuN
…ent entity

A specialization stores no Persistable flag — its Generalization element has
no such property; the flag lives on the root's NoGeneralization. entityFromGen
defaulted it to true and never revisited it, so `describe entity` printed
`persistent entity Probe.Child extends Probe.Base` for a child of a
non-persistent Probe.Base, and the same wrong flag reached the bulk
`where non-persistent` filter, the catalog, the diagrams and the microflow
builder. The System module's HttpRequest/HttpResponse/SoapFault read
persistent for the same reason.

The domain-model reads (ListDomainModels, GetDomainModel, GetDomainModelByID)
now resolve each specialization's flag by walking its generalization chain
across every stored module and the virtual System module. entityUnchanged
takes the statement's value for a specialization, since the resolved flag is
not stored on the element and comparing it with the raw default would rebuild
every such entity on each domain-model write.

Fixes #1510 (mendixlabs#1600).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KjZvGAo8FZaqUbDAd2SdsH
…#1597)

`@annotation('Why this commit is here.')` in an `alter microflow … insert`
fragment passed check and exec ("Altered microflow") and no note was
written; the same line in `create or modify microflow` is refused as MDL079.

The annotation rules — MDL059, MDL060, MDL079, MDL092 — ran over create
bodies only. A fragment is built by the same flowBuilder, which drops an
annotation the visitor could not read, so all four reached the builder
unchecked in an alter. check now runs them over every insert and replace
fragment, and execAlterFlow refuses the exec-enforced subset as create does,
for `-c` and the REPL, which skip the check preflight.

Refs #1507

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HjYj5GTj2in3ckFYaDBAuN
A quoted attribute segment in DynamicClasses, DynamicCellClass or a
pluggable widget's Expression-typed property was stored with its quotes,
$currentObject/"Code", while ContentParams and Visible stored
$currentObject/Code. check passed; mxbuild reported CE0117 at the widget.

Every widget slot that stores expression text now strips identifier
quotes, as the microflow expression slots do. The mdl 0 read-back check
compares against the stripped text, so the quoted-expression rewrite is
still offered.

Fixes #1489 (mendixlabs#1584).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PEg7VrNNsoTxh9QXviwYgU
retrieve $Notes from M.Note where [IsOpen = true() and
	Count > 0];
was stored as [IsOpen = true() and Count > 0]. The visitor kept the
source; the shared XPath formatter sent every constraint with a line
break through the #979 canonical layout, which renders a short one flat.

A constraint that already holds a line break is now the author's layout
and is stored as written (operator case still folded). A flat constraint
is laid out as before. Describe prints a stored constraint on one line
only when its layout is the one the writer derives; an authored layout
is printed as stored, for retrieves and access rules alike, so
describe -> exec stores it again.

Fixes #1440 (mendixlabs#1542).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PEg7VrNNsoTxh9QXviwYgU
…lkTypedFlow)

checkFlowMembers carried the only walk that types a flow's variables for
the --references checks (parameters, create, database and association
retrieves, loop iterators, nested blocks and error handlers). The
Protected-module member check needs the same typing, and a second copy
would drift on which statements type a variable (the
duplicate-resolver-drift class), so it becomes walkTypedFlow with a
visit callback. visit runs before a statement's own output is typed,
which is the order the old inline checks relied on. No behaviour
change: the member-reference tests pass unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxAn3VqxUnZ7ZGUnzAYpbT
…dule (CE1406, mendixlabs#1498)

`check -p --references` passed a flow in an ordinary module that used a
Hidden attribute or association of a Usable entity in a Protected
(add-on) module. mxbuild refuses it: "… is not allowed to use attribute
'AddOn.Request.Payload' because app modules cannot reference hidden
elements in add-on modules" (CE1406).

checkFlowMembers resolves the members a flow uses, over walkTypedFlow:
- change / create members (attributes, through generalizations, and
  associations)
- association retrieves
- XPath steps of a database retrieve
- `$v/…` expression paths in return, declare, set, decision, while,
  log, call arguments and member values

It reports CE1406 when the member's module is Protected, the flow's is
not, and the member is Hidden. The level is the one the script states,
else the stored one, else Hidden for a member the script creates. The
hint is the alter statement that makes the member Usable, with the
stored type restated. A use it cannot resolve is silent.

Measured on 11.14.0, one microflow per shape: all nine shapes are
CE1406 while the member is Hidden. The Usable controls, use inside the
add-on, and the whole script in an ordinary module give 0 errors. check
now reports the same nine (4 attribute, 5 association) and nothing for
the controls. After the two hinted alters, check passes and mxbuild
reports 0 errors. The new bug-test: 0/0 as written; with its clauses
removed, 8 CE1406 from check and from mxbuild.

Control: with checkFlowMembers stubbed, all 14 refusal cases of
TestValidateProtectedModuleRefs fail with no error reported, the
reported symptom.

Not covered: page widgets binding a Hidden member.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxAn3VqxUnZ7ZGUnzAYpbT
return $Parent/Mod.Child_Parent; passed check --references and was
CE0117 at the end event; loop $c in $Parent/Mod.Child_Parent was written
with no list (CE0099), because the visitor read only a bare variable and
dropped the path the grammar accepts.

MDL049 now covers the return value of a microflow or nanoflow, with its
existing exemptions (the target-entity step builds clean, measured), and
MDL094 refuses a loop over any path. The loop path is kept in the AST
instead of being dropped, and MDL094 is exec-enforced since the builder
has nowhere to store it.

Fixes #1231 (mendixlabs#199).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PEg7VrNNsoTxh9QXviwYgU
…ssing name, describe the qualified one

A Custom chart's `eventDataAttribute: EventData` inside a data view over a
page parameter whose entity has no EventData passed check and exec and only
mx check reported CE1613. misboundAttributeError refused a bare name only
when another entity in scope had it; a name no entity has is now refused too,
by check and exec alike.

Bound to another entity (MyFirstModule.ChartContext.EventData), the property
was stored qualified but described bare, so re-running the description rebound
it to the data view's entity. The generic pluggable describer now keeps the
qualified name for an unlinked attribute of another entity, and a data view
over a page or snippet parameter puts the parameter's stored entity in scope
rather than its name.

Mirror of mendixlabs#1591 (#1502).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SiV1dB1yMWeXnjFhWFDSvg
…ociation (mendixlabs#1599)

DESCRIBE PAGE printed a gallery whose "Database" source runs over an
association as `$currentObject/Assoc` with no `sort by` and no `where`, and
`create page` refused the faithful `database from $currentObject/Assoc/Entity`
form for anything but a list view.

Studio Pro stores that source as a CustomWidgets$CustomWidgetXPathSource with
an IndirectEntityRef, its XPath and sort bar beside it. The reader treated it
as an association source (the #941 reading), which drops both and replays as
a Forms$AssociationSource — a different retrieve.

- Reader: a pluggable XPath source with complete hops reads through
  parseEntityPathSource, as the list view source already does; a hop without a
  stored destination keeps the association reading.
- Writer: on 10+ buildPluggableDataSourceV3 builds the form and
  customWidgetDataSourceToGen stores the hops as an IndirectEntityRef with the
  start parameter in SourceVariable (9.x keeps the #1399 database source).
  Widgets with their own builder still refuse it in buildDataSourceV3.

Measured on 11.12.1: the reporter's shape authored in MDL, mx check 0 errors,
describe -> exec "Unchanged page"; with the association byte-patched to a
nonexistent name mx check reports CE1613 at Gallery 'g1'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hjeuk6NPBWq3QTxCKxsARF
…by key (mendixlabs#1586)

DESCRIBE PAGE omitted expression-typed (and other) settings of the widgets
with a dedicated describe reader: a combo box's customEditability /
customEditabilityExpression / ariaRequired, a text or number filter's
defaultValue expression and delay. Exec wrote them; describe -> edit -> exec
reset them.

The report's own case, a generic pluggable widget's top-level expression with
a false MDL-WIDGET06 warning, reproduces only on the binary before
mendixlabs#1578 and is already fixed on main.

- The combo box and the four data grid filters now run the by-key reader
  (mendixlabs#1573) beside their keyword reader, skipping the keys it already prints.
- Values are compared with what exec writes for an absent key (the embedded
  template), not the schema DefaultValue: the combo box template stores
  readOnlyStyle `bordered` against a default of `text`, so Studio Pro's combo
  boxes (Administration.Account_Edit) lost `text` on an edit-replay.
- A combo box's database/static-mode settings, a mode DESCRIBE does not spell,
  are named in a NOT re-executable note instead of printed.
- `check` without -p tops the hand-crafted definitions up from the embedded
  template, so describe output passes it as it does with -p.

Corpus diff against a baseline build over the Blank 11.12.1 app: only the
intended lines change, every description replays "Unchanged", mx check 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hjeuk6NPBWq3QTxCKxsARF
…bs#1520)

The snippet half of the report: a data view sourced from a snippet parameter
described without its DataSource. Already fixed by the snippet-parameter slot
read (#721 L3); retested on 11.12.1, describe prints
`DataSource: $View` and replays "Unchanged snippet". The page half was fixed
by #1416.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hjeuk6NPBWq3QTxCKxsARF
…ame (mendixlabs#1588)

`create java action` had no way to set a parameter's description or the
return value's name. `describe` printed both as `--` comments
(`Input: String not null  -- The text to convert.`, `-- return NAME:
'UpperText'`), which read like syntax: written in a script they passed
check and exec and were dropped, leaving the description empty and the
return name `ReturnValueName`.

Both now use the spelling the language already has for the same thing
elsewhere:

  - a `/** … */` doc comment before a parameter is its description, as on
    an entity attribute;
  - `returns T as $Name` names the return value, as on a microflow.

Java and JavaScript actions share these grammar rules, so both visitors
read them through one helper and both describers print them through one
emitter; describe output now re-executes as "Unchanged". On `create or
modify` an omitted description or name still keeps what is stored. A
multi-line description, of which the `--` form printed only the first
line, now round-trips whole; one holding `*/`, which a doc comment
cannot carry, is still shown as a comment so the output parses.

Refs #1496

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HjYj5GTj2in3ckFYaDBAuN
claude and others added 29 commits October 21, 2026 05:46
Ported from #1527 so this PR's build-and-test can go green:
govulncheck fails on crypto/x509@go1.27.2 (GO-2026-6809, fixed in
1.27.3), reached through internal/marketplace/client.go. main is red on
the same check. Identical change; it no-ops once #1527 lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hjeuk6NPBWq3QTxCKxsARF
govulncheck now fails build-and-test on Go standard-library advisory
GO-2026-6809: "Distrust compromised intermediate certificates in
crypto/x509", found in crypto/x509@go1.27.2 and fixed in go1.27.3. The
reachable trace is marketplace.Client.Download -> io.Copy ->
x509.Certificate.Verify. main pins go1.27.2, so every PR is red on it
(first seen on #1519).

Toolchain go1.27.3 in go.mod and in the workflows' setup-go. The go
directive stays 1.26.0. The devcontainer needs no change: it sets
GOTOOLCHAIN=auto, so go.mod's toolchain line decides.

govulncheck with go1.27.2: "affected by 1 vulnerability" (GO-2026-6809).
With go1.27.3: "affected by 0 vulnerabilities". make build, make test
and make lint pass on go1.27.3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxAn3VqxUnZ7ZGUnzAYpbT
(cherry picked from commit a3b33a8)
… brackets

A Studio Pro constraint can end in a line break (WorkflowCommons'
WorkflowUserTaskView rule). Described as stored, it failed
IsBracketedXPath and fell back to the deprecated quoted form, which
broke TestStringLiteralsRoundTripUnderMdl1 on TestApp. Whitespace outside
the brackets is now trimmed, as flattening always did.

WorkflowCommons.WorkflowAttachment and WorkflowComment stay on the
TestApp allowlist: their stored constraints end in a line break too, and
since a rule is keyed by role plus constraint text (#936), the trimmed
spelling adds a rule beside the stored one, as the flattened one did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PEg7VrNNsoTxh9QXviwYgU
Ported from #1527: govulncheck fails every build on the
crypto/x509 vulnerability fixed in go1.27.3, on main as on this branch.
No-ops once #1527 is merged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PEg7VrNNsoTxh9QXviwYgU
fix(deps): keep the klauspost/compress floor across go mod tidy
fix: silent-property-drop cluster — loop @Curve, alter annotations, Java action descriptions
build: move to Go 1.27.3 for govulncheck (GO-2026-6809)
fix(executor): check a flow's use of Hidden members of a Protected module (CE1406, mendixlabs#1498)
Brings in #1527 (Go 1.27.3), which makes this branch's cherry-picked
copy of the same bump a no-op, and resolves the CHANGELOG conflict:
main moved the Unreleased "Fixed" entries into a second block, so the
resolution is main's side plus this branch's one new entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KjZvGAo8FZaqUbDAd2SdsH
fix: describe round-trip gaps on pages (gallery sort over association, by-key combo box / filter settings)
…ig.yaml (mendixlabs#1509)

QUAL004 (orphaned elements) hard-coded its entry-point prefixes and
CUSTOM002 (microflow prefix convention) its valid prefixes, and `mxcli init`
rewrites the shipped rule files, so a project with its own naming
convention could not keep an edit: the reporting project got 84 QUAL004 and
121 CUSTOM002 findings for TEST_/GET_/PRS_ microflows.

Both rules now read an option - rules.QUAL004.options.entry_point_prefixes
and rules.CUSTOM002.options.valid_prefixes - as a YAML list or
comma-separated text. A configured list replaces the built-in one; unset,
the built-in list applies as before.

Underneath, goValueToStarlark converted scalars only, so any YAML list or
map under a rule's options reached the rule as None, indistinguishable from
"unset". Lists and string-keyed maps now convert recursively. A new
get_list_option(key, default) builtin reads a list of strings in either
spelling. An option value a rule cannot use is recorded and reported after
the run as a warning naming rules.<ID>.options.<key> (RuleFailure, so it
stays out of the score) while the rule runs on its default.

QUAL004 still counts only callers in the model; a microflow called only
from tests/*.test.mdl is reported, and its prefix belongs in the option
(maintainer decision).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3x8Fyvir8kLyPgJs1BDmF
fix: binding-context cluster — pluggable attribute scope, control-bar SHOW PAGE selection
fix: check-references cluster — move/rename targets, page XPath script members, ALTER PAGE column targets
fix: expression-translation-drift cluster (#1231, #1440, #1489)
… properties (mendixlabs#1511)

A snippet call renders no element of its own: the snippet's widgets are
inlined into the parent, so the Class, Style, DynamicClasses and design
properties stored on a Forms$SnippetCallWidget never reach the DOM. check,
exec and mx check were all clean, so the setting was silently useless.

Measured on Mendix 11.15.0 (run --local + playwright check): a snippetcall
with Class 'probe-x', Style 'border: 3px solid red', DynamicClasses
'probe-dyn' and 'Align self' / 'Hide on' produced no element carrying any
of them (the snippet's container was a direct child of the enclosing
container), while a container on the same page with the same four settings
carried every one.

- MDL-WIDGET48 (check, warning): in the widget-tree walk for CREATE PAGE /
  SNIPPET and ALTER PAGE INSERT / REPLACE; and in the reference pass for
  `alter page ... { set (Class|Style|DynamicClasses|<design prop>) on sc }`
  and `alter styling ... widget sc set ...`, where the stored widget's $Type
  comes from the page mutator. Clearing a value stays silent.
- MPR013 (lint, correctness/warning): catalog Class/Style/DynamicClasses,
  plus design properties read from the stored document.
- Docs: lint rule table, create-page skill + widget reference, docs-site
  widget types, `syntax snippet` help.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3x8Fyvir8kLyPgJs1BDmF
`create workflow … call workflow M.Child … boundary event interrupting timer …`
failed to parse ("mismatched input 'boundary' expecting ';'"), although the
reader, writer, describer, ALTER mutator and MCP backend all handle
Workflows$CallWorkflowActivity.boundaryEvents (10.14+, the same floor as every
other owner). DESCRIBE printed the clause for such an activity, so its output
did not re-parse.

workflowCallWorkflowStmt now takes call microflow's exact boundary-event clause
(no new tokens), the visitor fills a new BoundaryEvents field on
WorkflowCallWorkflowNode, and buildCallWorkflowActivity writes them through
buildBoundaryEvents.

The AST walkers each switched on the boundary-event owners themselves and none
knew call workflow, so WF07/08/09/10/15/18/19, the rewrite guard's count and the
11.11 notification gate would have skipped the new bodies. A single helper,
workflowActivityBoundaryEvents, now lists the owners; the two walkers that label
by owner (WF15, WF19) gained the case. On the stored side, ALTER's end-ancestry
walk lost the non-interrupting context under a call workflow and MDL-WF05 did
not see jump targets inside its boundary body; both gained the case.

Measured: interrupting and non-interrupting timer boundary events on call
workflow, mx check 0 errors on 11.6.6 and 11.15.0; describe -> exec "already in
sync" on both. A notification boundary event on call workflow is 0 errors on
11.15.0 and refused by the existing 11.11 gate on 11.6.6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3x8Fyvir8kLyPgJs1BDmF
…d on (mendixlabs#1518)

An object created with commit = No exists only in memory. If the
microflow never commits it, returns it or hands it to anything that
could, it is discarded when the microflow ends - and check, mx check and
the build are all silent. In the field an after-startup seed microflow
logged "Seeded 20" and the table had 0 rows.

CONV020 (warning, category quality) walks each microflow's stored object
graph via FullMicroflow, like CONV011. It reports a CreateObjectAction
with an explicit Commit = No whose entity the catalog lists as
persistent, when nothing else in the flow uses the output variable except
to read a member ($o/Attr) or change it without commit.

Every other mention counts as passing the object on: the walker scans
every string of every object in the flow (loops and error-handler paths
included) by reflection, so a field holding the variable name (commit,
delete, rollback, page passed object, workflow context, export mapping
source, ...) or a bare $o in any expression (return value, call
arguments, list change, association value) keeps the rule quiet,
including action types the rule does not know. It is flow-insensitive on
purpose: a commit on one branch only is an accepted false negative.
Nanoflows, rules, non-persistent entities, an unstored commit value and
System/Marketplace modules are out of scope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3x8Fyvir8kLyPgJs1BDmF
… transformers (mendixlabs#1510)

lint QUAL002 asks for documentation on import and export mappings,
published REST services and data transformers, and MDL could not write
it: the four create statements had no Documentation field, so the
`/** ... */` doc comment above them was dropped with MDL089 and the
findings were permanent. Two more defects kept them that way:

- every create or modify wrote an empty Documentation over the stored
  text (the executors never set it; the data transformer writer wrote a
  literal ""), so describe -> exec of a Studio Pro-documented mapping or
  transformer deleted its documentation;
- the catalog wrote "" for published_rest_services.Documentation, so
  QUAL002 reported every published REST service, documented or not.

The doc comment is now each statement's documentation, as on a JSON
structure: the visitor reads it with its presence bit, the executors set
it and carry the stored text when no comment is written (#1018), the
modelsdk backend reads and writes the top-level `Documentation` key for
published REST services and data transformers (verified against
modelsdk/gen and against Mendix's own reader with `mx dump-mpr` on 11.6.6
and 11.15), describe prints it back, and the catalog stores it.
PublicDocumentation (the OpenAPI text) keeps being carried as stored.

Java action parameter descriptions were already writable (mendixlabs#1588); the
bug-test script covers them alongside.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3x8Fyvir8kLyPgJs1BDmF
… create (mendixlabs#1510)

A doc comment above a create that stores no documentation was warned
with "Move the doc comment directly above `create json structure ...`,
after the `create or modify published rest service ...`" -- moving text
written for one element onto a different one. The next-statement
suggestion is right when a drop, grant, revoke, set or alter stands
between a comment and its create (#877), but a create is itself
the element the comment describes.

For a create, MDL089 now says MDL cannot store documentation for that
element and suggests a `--` comment; the move suggestion stays for the
other statements. The check-syntax skill says the same (and no longer
lists `create module`, which stores documentation since #1314).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3x8Fyvir8kLyPgJs1BDmF
fix: describe prints persistent for a specialization of a non-persistent entity
…-events

feat(workflow): boundary events on call workflow (mendixlabs#1532)
…e-warning

feat(check,lint): warn when a snippet call carries Class/Style/design properties (mendixlabs#1511)
…ver-persisted

feat(lint): CONV020 — object created in a microflow and never committed, returned or passed on (mendixlabs#1518)
Resolve the CHANGELOG conflict with #1532 (CONV020) by keeping both
"Added" entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3x8Fyvir8kLyPgJs1BDmF
…ings-rest-javaparams

fix(mdl): document import/export mappings, published REST services and data transformers (mendixlabs#1510)
…ixes

feat(lint): configurable QUAL004 entry-point and CUSTOM002 prefixes (mendixlabs#1509)
@ako
ako merged commit adc0d35 into mendixlabs:main Oct 21, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants