Skip to content

Sync ako/mxcli: Business Events channel identity, describe/write round-trip fixes, more check coverage - #1564

Merged
ako merged 45 commits into
mendixlabs:mainfrom
ako:main
Oct 17, 2026
Merged

ako merged 45 commits into
mendixlabs:mainfrom
ako:main

Conversation

@ako

@ako ako commented Oct 17, 2026

Copy link
Copy Markdown
Collaborator

Syncs ako/mxcli:main into upstream: 24 commits from fork PRs ako#1390, ako#1416–ako#1419 and ako#1421–ako#1431.

Writes that changed or lost what was stored

check catches more of what mxbuild refuses

Parse-error hints

Commands

Docs

  • The xpath-constraints skill line index has been regenerated.
  • Each fix records its finding

claude and others added 30 commits October 15, 2026 21:53
…e XPath (mendixlabs#1466)

After MDL055 refused two hops, two one-hop comparands still passed check and
failed mxbuild with CE0161: a hop landing on an entity unrelated to the left
side's (MDL-XPATH02, also association vs attribute) and an unqualified
comparand association (MDL-XPATH01, page arm). Runs in the no-project tier from
the script's declarations, the project filling in the rest; silent where an
entity or generalization chain cannot be established. Stubbing it fails 9 tests.

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

The issue's own script omitted CaptionAttribute on both combo boxes; check and
exec passed and mxbuild reported CE0642 "Property 'Caption' is required." per
combo box. MDL-WIDGET16 returned clean once a data source was present. It now
requires the caption the caption type selects (attribute, or type expression +
expression), measured on fresh 11.13.0/11.14.0 projects; enumeration mode is
not judged. Test table mirrors the measurement; stubbing the check fails 8 cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VKmbj53BC2vuUWYmgWgfQ5
The skill grew with the comparand note; make check-skill-index refuses stale ranges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VKmbj53BC2vuUWYmgWgfQ5
…ox-caption

fix(executor): refuse the remaining #1466 shapes — XPath comparand type and combo box caption (mendixlabs#1466)
…ify (mendixlabs#1525)

createBusinessEventService called generateChannelName() for every message
on every build, including CREATE OR MODIFY of an existing service. Three
runs of an unchanged script gave three different ChannelNames, each
reported "Modified". The ChannelName is the Kafka topic a deployed app
publishes on, so every re-run moved every topic and disconnected
subscribers in other apps, with a clean mx check; and the rewrite could
never be elided (ADR-0008).

On a rewrite, carry the stored definition $ID and, per declared message
matched by message name, the stored channel's $ID and ChannelName, the
message $ID, attribute $IDs (by attribute name) and the operation $ID. A
new message gets a fresh channel name. A stored channel holding several
messages is handed to the first one only, so no two channels share a
topic.

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

The gen->sdk workflow reader converted a CallWorkflowActivity's Workflow,
ParameterExpression and boundary events but never its ParameterMappings, so
describe printed `call workflow M.WF_Child;` for a call stored with
`(WorkflowContext = $WorkflowContext/M.Parent_Current/M.Item)`. Re-executing
that output let autoBindCallWorkflow write the default `$WorkflowContext`,
silently passing the parent's context object instead (CE0117 on 11.15.0).

Add workflowParamMappingsFromGen, the call-workflow twin of
microflowParamMappingsFromGen, and wire it into the CallWorkflowActivity case.
A writer->gen->reader unit test covers the reader layer the describe tests
skip, and an integration test proves describe -> exec reports "Unchanged
workflow" with the mapping intact. Measured on 11.15.0: the pre-fix round
trip goes 0 -> 1 error (CE0117); the fixed one stays at 0.

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

A message attribute named like a member the event entity inherits from
BusinessEvents.PublishedBusinessEvent (the reporter's EventId) passed check
and exec; mxbuild then reported "The Business Events attribute 'EventId'
is not present on the entity." (measured on 11.6.6 and 11.15.0 with a stub
base entity; the control without the attribute is clean of it). An
inherited attribute does not count and cannot be redeclared, so such a
message can never be satisfied.

The --references pass now walks the message entity's generalization
chain, script-declared entities first and then the project, and reports
a message attribute an ancestor declares. The inherited members are read
from the model, not a list in mxcli; where the chain cannot be read
(no BusinessEvents module) nothing is reported.

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

DESCRIBE PAGE left out a Data grid 2 column with no attribute and an empty
header (the usual actions column, ShowContentAs: customContent) together
with every widget inside it. Re-executing the unchanged output was a no-op
("Unchanged page"), but the first real edit replaced the page and deleted
the column and its buttons from the model, with mx check clean.

Cause: extractDataGrid2Columns took the first property holding Objects and
kept a column only when it had an attribute or a caption.

Fix: find the `columns` property by its resolved key and treat every object
in it as a column; the old heuristic remains only for an unresolved key,
widened to columns with content or a content mode. A column with neither
attribute nor caption is printed with `Caption: ''`, which the writer
stores as the empty header template. Measured on 11.6.6 and 11.15.0:
describe -> edit -> exec keeps the column and button, mx check 0 errors.

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

A workflow has exactly one parameter and mxcli always stores it as
WorkflowContext; the name in a `parameter $Task: M.Item` header is only an
alias. buildCallWorkflowActivity wrote the mapping key as
<called workflow>.<key> verbatim, so `call workflow M.WF_Child(Task =
$WorkflowContext)` passed check and exec and failed the build with CE1613
"The selected parameter 'M.WF_Child.Task' no longer exists" (measured on
11.6.6 and 11.15.0). The syntax help taught that form.

New rule MDL-WF18 refuses any other key at check time (create and the
activities an alter inserts) and at exec, naming WorkflowContext; a casing
variant is accepted and written in the stored spelling. The syntax-help
example, the quick reference and the write-workflows skill now show
`(WorkflowContext = ...)`. Measured: the corrected form is 0 errors on both
versions; the repro is refused by check and by exec --no-check.

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

`create or modify navigation Responsive { menu item ... }` cleared the
profile's home page, role-based home pages, login page and not-found page,
so the next build failed with CE0527 "Select a default home page for this
profile". navPatchWebProfile wrote those four fields unconditionally from
the spec, while the menu, sync block, ON SYNC ERROR and PROGRESSIVE WEB APP
were already left alone when omitted. navPatchNativeProfile rebuilt
RoleBasedNativeHomePages the same way.

An omitted clause now keeps the stored value for every field. `home ... for
<role>` replaces that role's home page only; a role the statement does not
mention keeps its own (navMergeRoleHomes). MDL has no clause that clears a
home, login or not-found page, so such a page can no longer be removed by
leaving it out. describe -> exec is unaffected, since describe states every
clause. Both patchers now work on a copy of the stored profile, because
navSetField writes in place.

The docs said both "fully replaces" and "omitting the block leaves it
alone"; syntax help, skills and docs-site now describe the one rule.

Measured with mx check after a menu-only statement on a profile with a home
page: 11.15.0 and 11.6.6 both CE0527 (1 error) before, 0 errors after.

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

fix(executor): describe keeps an untitled Data grid 2 actions column (mendixlabs#1520)
`dropdownfilter f (attributes: [Status])` stored attrChoice "auto" with an
empty `attr`. check, exec and mx check stayed clean, describe printed a
bare `dropdownfilter`, and in a gallery's filter bar (no column to inherit
an attribute from) the filter failed at runtime with "Unable to get filter
store".

Cause: dropdownfilter.def.json mapped `Attributes:` onto `attributes` and
`defaultFilter`, the text filter's keys. The DataWidgets 3.x drop-down
filter (3.4.0 in 11.15; same keys in the 11.6 template) has one attribute
slot, `attr`, which its editorConfig shows only under attrChoice "linked".
Every builder Set* operation skips a key the template does not declare, so
the value was dropped without a word.

Fix:
- def: a new `attribute` mode (condition `hasAttributes`) writes
  attrChoice=linked and the attribute on `attr`; a bare filter keeps "auto".
  `Attributes:` feeding the single-attribute op resolves one attribute and
  refuses a list of more (exec, and MDL-WIDGET47 at check).
- engine: an AUTHORED value mapped to a key the loaded template does not
  declare now fails the statement (optional backend.WidgetPropertyDeclarer,
  implemented by widgetobj.Builder; the MCP builder cannot answer and is
  unchanged). Mapping constants are exempt.
- check: widgetValueMap read the constants of every mode, last one winning,
  so the new `attr` was reported hidden under the default mode's "auto" and
  the association mode's baseType "ref". Inactive modes' constants are now
  ignored.
- describe: reads `attr` back as `Attributes: [A]` under attrChoice linked.

Measured (fixture copies, create + mx check): 11.6.6 and 11.15.0 both store
attrChoice=linked, attr=Tmp.Item.Status for the column and the gallery
filter, 0 errors, describe prints `(Attributes: [Status])`. Before: auto,
empty attr, 0 errors, bare describe.

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

`call microflow M.ACT_Bind(Workflow = $Workflow, Item = $WorkflowContext)`
in a workflow passed `check --references` and `exec`, then the build failed
CE0117 "Error(s) in expression.". No workflow rule looked at the variables an
expression names, and the code asserted the context was the only one in scope;
`$WorkflowInstance` (System.Workflow) was nowhere in mxcli. The same root cause
behind mendixlabs#1523: the unmapped-parameter error suggested the
deprecated `with (Workflow = ...)` and never what value to pass, and with no
argument list auto-bind mapped every parameter, a System.Workflow one included,
to $WorkflowContext.

Scope measured with mx check on 11.6.6 and 11.15.0 (identical verdicts):
$WorkflowContext and $WorkflowInstance build in call microflow arguments,
decisions, wait-for-timer, user task due dates, boundary timers and event
sub-process timers; the workflow's own header due date sees only the context.
$Workflow, $currentUser, $currentSession and $WorkflowUserTask are CE0117
everywhere, and so is a lower-case $workflowInstance as stored.

- MDL-WF19 (validate_workflow_scope.go): every $name outside a string literal
  in a workflow expression must be WorkflowContext (any case), the header's
  declared name, or WorkflowInstance (not in the header due date). Applied by
  check to CREATE and ALTER, and refused by exec too, since exec is reachable
  without check. The message names $WorkflowInstance for $Workflow.
- Auto-bind maps a System.Workflow parameter to $WorkflowInstance.
- The unmapped-parameter error names the current `call microflow M(P = v)`
  form, with $WorkflowInstance / $WorkflowContext by parameter type.
- The writer rewrites the declared context name and casing (and now
  $WorkflowInstance casing) at every expression site; boundary timers, event
  sub-process timer starts, call-workflow arguments and the header due date
  used to be written verbatim, which the rule's allow-list would otherwise
  have hidden.

mx check on a fixture copy: the probe set went from 10 x CE0117 to 0 errors
on both versions once the out-of-scope names were refused and auto-bind fixed.

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

fix(workflows): call workflow parameter mapping — describe keeps it, check refuses a non-WorkflowContext key (mendixlabs#1531, mendixlabs#1530)
…s#1522)

MDL078 reported a glyph code the Mendix glyph font does not define as a
warning, so exec wrote it, mx check passed it at 0 errors, and
`mxbuild --target=deploy` crashed: exit 1, InvalidOperationException
"Sequence contains no matching element" at GlyphFont.GetClass, a layout
named instead of the icon, and no --write-errors file for a build gate to
read. A real model error in the same build exits 3 and writes the file.

The warning hedged against the font table being a snapshot that Mendix
might extend. That was measurable: mxbuild bundles its own
modeler/glyphicons-halflings-regular.ttf, byte-identical in 9.24, 10.12,
11.6.6 and 11.15.0, and its cmap's private-use range is exactly the 247
codes in glyphIcons. MDL078 is now an error, so check fails and exec
refuses before writing. Every carrier (navigation menu items, menu
documents, page/snippet/layout widgets) goes through the same validator.
A glyph has no name, so the suggestion names the defined glyphs either
side of the bad code (57388 -> 57385 play-circle, 57392 repeat).

navigation-glyph-code.mdl becomes a .fail.mdl, since check now rejects it.

Measured with the reported code 57388: before, exec applied it, mx check
0 errors, mxbuild deploy exit 1 with no error file (11.15.0 and 11.6.6).
After, check reports MDL078 as an error and exec prints "Refusing to
execute ... Nothing was written".

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

`set (ColumnWidth: manual, Size: 220) on dg column(Name)` followed by
`set (ColumnWidth: autoFill) …` stored width=autoFill with size=220, and
mx check reported CE0463 on the Data grid 2.

Cause: setColumnPropertyMut writes exactly the property named. Data grid 2
hides `size` unless width is "manual" (and `minWidth` unless width is
"autoFit", `minWidthLimit` unless minWidth is "manual"), and a hidden
property holding anything but its default is CE0463. The create path writes
hidden properties at their default; ALTER never re-evaluated them.

Fix: after a column SET the executor hands the column's visibility rules —
lifted from the installed Data grid 2 package's editorConfig, not a list in
mxcli — to the mutator (optional backend.ColumnHiddenPropertyResetter).
pagemutator resets every primitive column property a rule now hides to the
default the stored Type declares, repeating until nothing changes, so a
reset that hides a further property resets that one too. A hidden property
the same statement names with a non-default value is refused instead of
silently overwritten (`ColumnWidth: autoFill, Size: 220`); its default
(`Size: 1`) is accepted. check judges a column SET as a whole as well as per
property, applying the same follow-up.

Measured (fixture copies, create → manual/220 → autoFill, then mx check):
11.6.6 and 11.15.0 both 1 CE0463 before, 0 after; size stored 1 after the
switch and still 220 under manual.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3x8Fyvir8kLyPgJs1BDmF
Resolve conflicts with #1418 (MDL-WF18): keep both the WF18 call-workflow
key check and the WF19 expression-scope check in execCreateWorkflow and the
ValidateWorkflow rule list; regenerate the write-workflows skill index.

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

An alter block's operations are aimed at the flow as stored, but the block
writes one flow. Three scope verdicts were taken per operation, or from
text that hides part of the fragment:

- checkOutputUnused ran per operation, in order, against the stored
  candidates, so a consistent rename (producer, reader, return in one
  block) was refused "$Code is still used by: log ...; return $Code" by
  readers that later operations replace. The checks now run once every
  operation is applied; a return value or condition set in place marks
  its node rewritten, so its stored reads retire and its new ones count.
- checkFragmentScope ignored what earlier operations of the block declare
  (unscopedRefs honoured it, but without position), so a fragment reading
  a variable an earlier insert declares upstream of it was refused MDL090.
  Each declaration now records the operation and stored target that put
  it in, and declaredOnPath credits it only where that site is upstream of
  the later operation's position; both checks use it.
- the scope check read a fragment's variables from formatActivity, which
  renders a loop as its header only, so a replaced loop's body was never
  checked: a body reading an undefined variable was "Altered" and mx check
  gave CE0109 (11.6.6 and 11.15.0). fragmentReads now walks loop bodies,
  with the iterator and the body's own declarations local to it.

The output check is shared with `create or modify`'s splice, so moving a
retrieve and its only reader into a sub-flow is now spliced instead of
refused "$Reporter is still used by" (the issue's repro A). Two tests used
that false refusal as their vehicle for check/preflight prediction of a
call to a flow the script creates; they now use a loop-body change, which
is still refused.

One commit: the A2 repro also needs the B2 fix (the replaced reader reads
the $Code2 the first operation declares), and the three share the test
harness. Each has its own revert control.

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

The MDL-V1-REBUILD refusal of a `create or modify` that changes a loop
body named only the replace-loop alter, while every other splice refusal
also names the deliberate rebuild: drop the flow and create it in the same
script. Both now share that advice (deliberateRebuildAdvice), so check,
which quotes exec's refusal, says it too.

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

`formatDecimal($d, 2)` passes a number where the String pattern goes.
mxbuild refuses it with CE0117 on 11.6.6 and 11.15.0, but `mxcli check`
printed "Check passed!" with and without -p.

Cause: checkCallExpr compared argument counts only, although its comment
and the E005 func-arg-type registry entry promised kinds; and it ran only
in the catalog tier (TypeCheckProgram, `check -p --references`), so a
plain check missed even a wrong count such as
`formatDecimal($d, '#', 'en', 4)`.

Fix:
- checkCallExpr now reports E005 when an argument's inferred kind cannot
  stand in the parameter: Boolean / String / number / DateTime are
  separate families; Integer and Long are interchangeable and widen to
  Decimal; Decimal into an Integer/Long parameter is refused. Unknown,
  empty, enumeration, object and Any are always tolerated, date tokens
  are typed DateTime, and rows marked unverified are skipped.
- BuiltinCallHints runs E005/E006 in the no-project validator
  (checkExprFunctions, microflows and nanoflows) with the kinds of
  literals, parameters and declares. The catalog tier only runs on a
  script this tier passed, so the two never report the same call. Not
  exec-enforced: --no-check still writes.
- Every mismatch class and the signature rows below were measured with
  mx check on 11.6.6 and 11.15.0 (they agreed on all ~130 probes) and
  pinned in builtin_arg_types_test.go. Turning the check on surfaced
  arity rows that were already false E006s on the -p tier: find/findLast
  take a start index, max/min are variadic and accept DateTimes,
  dateTime/dateTimeUTC take 1-6 parts, formatDateTime(UTC)'s format is
  optional. formatDecimal's format is required (min 2, was 1).
  parseDecimal's second argument may be a Decimal default.
- The one new diagnostic over mdl-examples was a true positive:
  use-cases/02-agentic-search.mdl passed the quoted string
  '[%CurrentDateTime%]' to addDays (CE0117); unquoted.

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

run --local never set ScheduledEventExecution in the boot
update_configuration payload, so the runtime's own default NONE applied:
an app's scheduled events silently never fired, the only trace being a
"Synchronizing scheduled events: None" runtime log line.

RunLocal now defaults ScheduledEventExecution=ALL after building the
runtime settings, unless the user passed --runtime-setting
ScheduledEventExecution=..., which still wins. It prints one line at
boot naming the effective value, the number of scheduled events in the
model, and how to override it. The shared runtimeConfigParams payload is
unchanged, so the test runner (StartLocalApp) keeps NONE and test runs
stay deterministic; a test pins that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3x8Fyvir8kLyPgJs1BDmF
fix(executor): an undefined glyph code is an error, MDL078 (mendixlabs#1522)
…of install (mendixlabs#1527)

A headless module or widget install leaves CE0463 (stored widget
instances older than the installed packages) and, for a module, CE6087
(renamed design properties) for Mendix's own tools to fix. install only
printed 'mxcli fix widgets' / 'mxcli fix design-properties' as a Next
step, so the field run was left with 41 units of CE0463.

install now runs them itself through docker.RunToolPreservingFormat, the
same call 'mxcli fix' makes, which keeps MPR v2: both repairs after a
transplant module install, the widget resync after a widget install
(online and --file). When the project's Mendix version cannot be read
or mx cannot be resolved, it prints a warning and the manual commands
instead of failing; a failing repair names its own command and the next
one still runs. The mx resolution and tool runner are package-level
seams so the tests need no mx and no marketplace network.

Not changed: marketplace update (still prints the steps), the
--allow-format-change module-import path, and dependency resolution.

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

Two bare parse errors in workflow MDL:

1. `call microflow M.ACT_Close as close` gave "mismatched input 'close'
   expecting {IDENTIFIER, QUOTED_IDENTIFIER}" with no hint: the keyword
   hint only matched the fixed 41-word hintedKeywordSet. Now any
   identifier-shaped offending token whose raw expecting set contains
   QUOTED_IDENTIFIER (read before simplifyExpecting truncates it) gets
   reservedKeywordHint — such a token can only be a lexer keyword, and a
   quoted name is exactly what that slot accepts. QUOTED_IDENTIFIER, not
   IDENTIFIER, because the statement-start set also lists IDENTIFIER. The
   branch returns before the fixed-list loop, so no word is hinted twice.

2. `returns` after `exposed as workflow action …` (or folder / url / …)
   gave "mismatched input 'returns' expecting BEGIN", read as "an exposed
   microflow cannot return a value". The grammar fixes the return type
   before the option clauses; the error now says so. It fires only when
   the parser context is a create microflow / nanoflow / rule header —
   not inside the body, and not past it — so a stray `returns` in a body
   is not mis-hinted. No grammar change.

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

`create microflow M.F returns Boolean as $b` gave "missing '(' at
'returns'" and "missing ':' at 'Boolean'"; with a bare `$l: List of …`
parameter "missing '(' at '$l'" and "missing ')' at 'returns'". ANTLR's
recovery inserts the missing `(` and parses `returns Boolean` as a
parameter, so every message points past the real mistake.

The hint is keyed off the source line: `create [or modify]
microflow|nanoflow|rule <name>` followed by returns / `$` / as / begin
instead of `(`. A header with parentheses cannot match. deduplicateHint
already keeps the cascade on that line to one explanation. The follow-on
error on the body line is left alone. No grammar change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3x8Fyvir8kLyPgJs1BDmF
The #732 hint fired on any parse error on a line containing `\'` and
always said "Under mdl 1 a backslash is an ordinary character, so `\'`
ends the string literal". In a headerless (mdl 0) script the backslash
escapes the apostrophe and the literal does not end there, so the advice
pointed the wrong way.

build() now reads the escape rule the script was lexed with
(parser.HasStrictEscapes on the char stream from newScriptStream) and
hands it to the error listener; under mdl 0 the hint explains that `\'`
does not end the literal, that a trailing backslash must be doubled, and
that a doubled apostrophe means the same under every version. The mdl 1
text is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3x8Fyvir8kLyPgJs1BDmF
fix(workflows): check the variables a workflow expression names; $WorkflowInstance (mendixlabs#1529, mendixlabs#1523)
…-width

fix(pages): drop-down filter keeps its attribute; ALTER column SET resets hidden properties (mendixlabs#1519)
…alized (mendixlabs#1521)

create or modify entity and alter entity ... modify attribute keep a
stored DateTime LocalizeDate when the statement does not state
localized / not localized (#743, #1373). That rule stays, but it was
invisible: a script that meant Studio Pro's default left a not
localized DateTime in UTC with no output saying so.

carryStoredLocalizeDate now reports whether it kept a stored false. The
create-or-modify path derives the kept attributes from the written
result (an unstated DateTime written not localized can only be the
carry), and both paths print one Note line per attribute naming it and
the `DateTime localized` spelling that changes it. Nothing is printed
when the clause is stated or the stored value is localized.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3x8Fyvir8kLyPgJs1BDmF
ako and others added 15 commits October 16, 2026 10:30
fix(exprcheck): check built-in argument kinds, without a project too (mendixlabs#1515)
…ject (mendixlabs#1516)

`retrieve $L from $h/M.Line_Header` from the TO entity of a Reference (the
"one" side) binds a LIST. Using it as an object -- `$L/Qty`, `change $L`,
passing it to an object parameter -- passed check --references and exec,
then mx check failed with CE0117 / CE7247 (measured identically on 11.6.6
and 11.15.0; the bare list retrieve, count and loop build clean on both).

MDL-RETRIEVE03 only seeded database retrieves: list-vs-object over an
association needs the association's type, owner and which end the start
variable is. The decision now lives in associationRetrieveYieldsList, used by
the flow builder and by a new --references pass that tracks each object
variable's entity, resolves the start end (script + project associations,
generalization chain) and reports member-path, change and object-parameter
use of the list under MDL-RETRIEVE03, through the walk the no-project rule
now shares. Owner Both (one-to-one) and the FROM side stay objects.

MDL043: with a project or a script that creates it, a bare declare type that
is an enumeration is not an object declare -- exec writes it as the
enumeration variable and it builds (11.15.0). It now warns with describe's
`enum` spelling instead of refusing it with the object wording.

Docs: microflow.variables documents the conditional-create idiom (one create
with conditional values, or a sub-microflow returning per branch; the same
name created in both branches is CE0111 on 11.15.0) and the enum declare;
microflow.retrieve and the error reference describe association list
results; MDL063's suggestion names the idiom for a create.

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

fix(executor): judge an alter block's scope against the flow it writes (mendixlabs#1517)
…of each kind (mendixlabs#1513)

scanWidgetOwnRefs collected every Microflow/Nanoflow/Entity/Form value in
a widget's own BSON but returned only the lexicographically smallest of
each, and the refs table is projected from that single widgets_data
column. A Data grid 2 (or gallery, list view) with `datasource:
microflow Tmp.Rows` and `onClick: call microflow Tmp.Open(…)` therefore
recorded only Tmp.Open: Tmp.Rows had no inbound reference, `show callers`
found none, and QUAL004 said "not called from anywhere. Remove if unused"
about a live datasource. The comment claiming page-level dedup made the
choice immaterial was wrong.

scanWidgetAllRefs now returns every name (sorted, deduped);
rawWidgetInfo.AllRefs carries them, buildPages/buildSnippets collect the
names beyond the single column onto the Builder, and buildReferences
inserts them after the widget projections with WHERE NOT EXISTS so a
page still gets one edge per target. The single columns keep their
choice (the smallest) for `show widgets` and Starlark's microflow_ref;
the lint-rules skill now says the column is one name and to ask
refs_to() instead. Datasource microflows keep RefKind `action`:
`datasource` is not in show callers' callerRefKinds.

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

The project id — the $ID of the Projects$Project root unit every module
points to, which Business Events uses as the app id — was shown by no
command, so two apps made from one copied model could only be found to
share it by grepping mprcontents. The model-engine backend's
GetProjectRootID was still the generated "unimplemented" stub; only
CreateModule reached the reader's method directly.

Backend.GetProjectRootID now delegates to the reader (struck off the
unreachable-unimplemented allowlist). describe settings prints it as a
`-- Project ID: …` comment, so the output still executes, and show
settings (table and JSON) adds a Project row. Read-only: no re-ID
command.

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

fix(check): refuse a list retrieved over an association used as an object (mendixlabs#1516)
fix(run): run --local runs scheduled events by default and says so (mendixlabs#1528)
…nd-returns-order

fix(visitor): parse hints for a keyword used as a name, misplaced returns, and a header without parentheses (mendixlabs#1533, mendixlabs#1514)
feat(settings): show the project id, read-only (mendixlabs#1526)
…otice

fix(executor): say when an unstated DateTime keeps its stored `not localized` (mendixlabs#1521)
…widgets

fix(marketplace): install runs the widget and design-property repairs itself (mendixlabs#1527)
fix(catalog): a widget that names two microflows keeps both references (mendixlabs#1513)
Resolve the manage-navigation skill index conflict with #1421 by
regenerating the skill index.

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

fix(navigation): an omitted clause keeps the stored value (mendixlabs#1522)
@github-actions

Copy link
Copy Markdown

AI Code Review

Critical Issues

None found.

Moderate Issues

None found.

Minor Issues

None found.

What Looks Good

The PR successfully syncs fixes from the ako/mxcli fork, addressing multiple important issues:

  1. Business Events channel identity preservation (create business event service cannot express unlimited attributes, collides with the CloudEvent id attribute, leaves V2 subject and channel name unmanaged #1525) - Fixes a critical data loss issue where re-running scripts would change Kafka topics
  2. describe/write round-trip improvements - Fixes for Data Grid 2 actions column (describe omits the root data source of a snippet and some widgets of a large page #1520), workflow parameter mapping (describe workflow drops the call workflow parameter mapping #1531), and dropdown filter attributes (Data grid 2 write defects: empty caption with customContent, dropdownfilter attribute, manual column width #1519)
  3. Enhanced validation - New checks for:
  4. User experience improvements - Better parse error hints, marketplace install running widget repairs, run --local starting scheduled events, settings commands showing project ID
  5. Comprehensive test coverage - Numerous new test files validating the fixes, including:
    • XPath comparand validation tests
    • Association list usage tests
    • Business event identity preservation tests
    • Dropdown filter attribute tests
    • Navigation omitted clause tests
    • Column hidden property reset tests
    • Workflow scope validation tests
    • Built-in function argument type tests

The changes follow established patterns:

  • Proper preservation of GUIDs and identities where required
  • Correct handling of optional vs required properties
  • Appropriate error messaging with MDL rule IDs
  • Well-scoped validation that doesn't overreach
  • Test files that clearly document the issues being fixed

The PR demonstrates good understanding of the system architecture, particularly around:

  • Identity preservation in Business Events
  • Widget property handling and visibility rules
  • Workflow execution contexts
  • XPath expression validation
  • Alter operation scoping

Recommendation

Approve the PR. The changes represent important bug fixes and validation improvements that enhance the correctness and reliability of mxcli. The sync from the fork brings in well-tested solutions to real user issues, and the accompanying test coverage provides confidence in the fixes. No violations of the review checklist were identified.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

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.

2 participants