Skip to content

Sync ako/mxcli: domain model SVG, SEC010 unreadable data, test ports, mxbuild watchdog - #1394

Merged
ako merged 39 commits into
mendixlabs:mainfrom
ako:main
Oct 12, 2026
Merged

ako merged 39 commits into
mendixlabs:mainfrom
ako:main

Conversation

@ako

@ako ako commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

Syncs ako/mxcli:main into mendixlabs/mxcli:main: 25 commits since v0.26.0, landed in the fork through PRs ako#1115–ako#1126.

Domain model diagrams

  • describe --format svg domainmodel <Module> writes a finished SVG: entities at their stored positions, coloured by kind, with attributes, associations with their multiplicities, and generalizations. No layout engine runs, so a client without a browser can show it as is. Boxes are sized for a proportional font, and the stored layout is pulled apart just enough to fit full names (capped at 2.5×) instead of clipping them. domainmodel is also available as --format elk.
  • Companion: on surfaces that draw SVG, selecting a Domain Model node shows the diagram with hover tooltips, at actual size with pan, fit and a full-width view.

Lint: data the user cannot read

  • SEC010 flags page data widgets (list views, data grids, reference selectors, pluggable grids, galleries and combo boxes) whose roles cannot read the entity. "Renders empty for them" is a warning, and XPath-only rules are info. A specialization's rule counts only for the members it grants. That case is a combo box over System.User, which rendered blank names for non-admins while check, mxbuild and the runtime log were clean.
  • System module access rules are measured from the model.mdp mxbuild writes (Mendix 11.15.0, Production security), since they are not in the .mpr.

Check

  • Flow verdicts are predicted for calls to flows the script itself creates. A change exec would refuse (MDL-V1-REBUILD) checked clean before, and exec wrote the earlier statements before refusing it.
  • A module-qualified association hop in an attribute path (Module.Assoc/Attr) parses, instead of falling through to a division.

Pages

  • DataGrid 2: an unnamed, uncaptioned column writes an empty header template, and an unbound attribute column writes an empty tooltip template, matching mx update-widgets (CE0463 before).

Testing

  • mxcli test --local takes --app-port / --admin-port / --serve-port, probes each before touching the project, and names the holding process when a port is taken.
  • A test whose call threw reports the raised message in its FAIL verdict. The test-microflows skill states that tests run with no current user.

Robustness: init, bootstrap, mxbuild

  • One-shot mxbuild runs (mxcli new, docker build) have a timeout and a stall watchdog (default 30 m / 10 m stall, configurable) that kills the whole process tree and reports the last output.
  • On macOS the project's JDK major is resolved through java_home, and /usr is never accepted as a Java home. Before, an 11.14+ project got the launcher stub and Gradle spun indefinitely during mxcli new.
  • mxcli init titles the generated docs after the .mpr, not the folder. The bootstrap script pins MXCLI_TAG to the generating release, and a failed database provisioning is reported loudly instead of passing silently.

Syntax reference

  • mxcli syntax no longer teaches spellings check flags as deprecated. A test holds every Example and Syntax block to the deprecation registry at error level.
  • New topic: microflow.workflow-actions.
  • Skill fixes: the create-page section index is regenerated, and bootstrap-app drops its dated Mendix version claim

ako and others added 30 commits October 11, 2026 20:39
describe --format svg domainmodel <Module> writes a finished SVG: entities
at the positions the model stores (where Studio Pro shows them), coloured by
kind, with attributes, associations and their multiplicities, and
generalizations. No layout engine runs, so a client with no browser (the
Claude Code companion) can show it as is. Associations between the same two
entities are drawn side by side; one to another module's entity, which has
no position here, is left out.

domainmodel is now a describe type with no MDL form, like systemoverview,
so --format elk domainmodel <Module> works too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Selecting a module's Domain Model node in the desktop app or VS Code shows
the SVG mxcli draws, with hover tooltips; the terminal lists the entities as
before. Diagrams are cached per model generation like MDL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ll-width view

A surface scales an Svg to its slot, so a 700-2200 px domain model shown
whole in the source column was too small to read. Diagrams now open at
actual size, cropped to the column through the root viewBox, with ◀ ▶ to
pan across; Fit shows the whole drawing; Hide tree gives the diagram the
pane's full width.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An interactive Svg draws in a sandboxed frame, which does not size itself
to its content the way an image does: with no width and height the domain
model showed as a ~90x110 px thumbnail in the desktop app whatever the
zoom. The element now gets its size in CSS pixels: the column's width at
Fit, the window's size at 100%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1103 added SubmitBehaviour and grid Width lines to the create-page
skill without regenerating its line-numbered index, so
make check-skill-index fails on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012PkPkaYM12u8yqMKNmzBgT
Boxes were sized for a monospace font and ran into the entities Studio Pro
places beside them. They are now sized for a proportional font, and rather
than clip names the way Studio Pro's fixed-width boxes do (a common
complaint), the stored layout is pulled apart along x, then y, just enough
for every box to fit its longest line, capped at 2.5x. Past the cap a box is
clamped to the gap and its text clipped, the tooltip keeping all of it.

On Evora's Teamcenter model: no clipping, the canvas 1.4x wider.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A test body whose CALL threw reported only "FAIL:exception during
execution": rewriteBodyForVerdict set a fixed verdict and discarded the
error. It now appends $latestError/Message, the expression throwsHandler
already uses (measured on mxbuild 11.6.0 and 11.13.0).

The test-microflows skill now states that tests run in a system context
with no current user: [%CurrentUser%] throws, $currentUser is empty, and
user-scoped logic is tested by passing the user as a parameter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012PkPkaYM12u8yqMKNmzBgT
`mxcli test --local` hard-coded 8081/8091/6544, so a machine with 8081
taken (an editor helper) could not run tests. The three flags `run --local`
has now exist on `test`, carried through RunOptions into the boot options
and the endpoint client; defaults are unchanged.

Each port is probed before the project is touched, and a taken one fails
with the port, its role, the flag that moves it and (on Linux) the holding
process. The flags are refused with --attach and on the Docker path, and
out-of-range or colliding values are refused up front.

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

A column with no caption, no attribute and no name had nothing to fall
back to, so applyColumnHeaderFallback left the header empty and it was
serialized as TextTemplate: null. mx update-widgets writes an empty
Forms$ClientTemplate there, so mx check reported CE0463 until the next
fix widgets, and every re-exec brought it back. Flag the header
EmptyTemplate instead, scoped to items with a header slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012PkPkaYM12u8yqMKNmzBgT
mxcli init named the project after the folder it ran in, so a cloud
session (which clones into a folder named after the repository) got a
CLAUDE.md titled "Mendix Project: seed" for an app called ChipCoV8, and a
devcontainer with the same name. Use the .mpr file name, falling back to
the folder when there is none, in init, add-tool and the --sync-skills
doc refresh (which would otherwise retitle it on the next session).

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

.claude/bootstrap-mxcli.sh fetched the nightly whenever MXCLI_TAG was
unset, so a project initialised by a pinned release got a different mxcli
back after every idle reap. Write the generating binary's version as the
default when it is a plain vX.Y.Z release; dev and nightly builds keep
nightly, since they have no fixed download.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012PkPkaYM12u8yqMKNmzBgT
The skill said the newest Mendix version "at the time of writing" was
11.14.0, a number that ages the moment it is written. Keep only the
procedure: probe the CDN for the newest version with both tarballs and
step back a minor on a 404. Regenerating the embed also refreshes
create-page's stale skill index.

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

resolveMacOSJavaHome hard-coded `java_home -v 21`, so for an 11.14+
(Java 25) project the answer always failed the major check and
resolution fell through to LookPath("java"). On macOS /usr/bin/java is a
launcher stub, not a symlink: the derived home was /usr, and isJDK
passed because the stub forwards -version to the default JDK. mxbuild
then ran with --java-home=/usr and Gradle spun indefinitely during
`mxcli new` (ChipCoV8 #3).

java_home is now asked for the required major, and on darwin a home of
/usr is rejected from every source. The resolver takes an injectable
environment so the macOS path is tested from Linux; the not-found error
names the major, the java_home query and the Temurin install command.

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

The SessionStart hook runs .claude/bootstrap-mxcli.sh under '|| true' so
it never blocks a session, and the script ended in 'exec ./mxcli run
--local --setup --ensure-db' — so a failure such as "no local PostgreSQL
superuser available" passed unnoticed. Run it without exec, tee the
output, and on failure print an ERROR banner to stderr naming the reason,
the retry command and --db-type hsqldb, then exit with the failure's
status. The hook keeps '|| true'.

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

docs(skills): regenerate the create-page section index
The `mxcli syntax` reference showed spellings `check` reports as
MDL-DEPRnnn, so an agent copying it got warnings (and refusals under
`mdl 1;` for `Date`):

- NOT NULL / UNIQUE [ERROR 'msg'] -> ERROR MESSAGE 'msg' (MDL-DEPR021),
  and say that an AutoNumber needs a DEFAULT seed (MDL023 / CE7247)
- named data grid and layout grid columns/rows -> COLUMN ( … ) (MDL-DEPR005)
- CREATE [OR REPLACE] for microflow, nanoflow, layout, navigation, and
  the create-modifiers topic -> OR MODIFY (MDL-DEPR001); OR REPLACE
  TRANSLATIONS keeps its own meaning and stays
- "MODEL accepts these keys" -> RUNTIME (MDL-DEPR555)
- MOVE doctype list: TASK QUEUE, CONSUMED/PUBLISHED REST/ODATA SERVICE,
  AI MODEL (MDL-DEPR550..553, 131)
- Date dropped from the attribute type list (MDL-DEPR160)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012PkPkaYM12u8yqMKNmzBgT
TestExamplesParse now runs every Example block through
executor.ValidateProgram with the deprecation policy at error, in every
context the block parses in, and adds data grid and data view hosts so
container-scoped rules (a named grid column, MDL-DEPR005) fire on a
fragment documented on its own.

TestSyntaxShowsNoDeprecatedSpelling covers the metasyntax field. Its
patterns come from the registry: each entry's Example/CanonicalExample
diff, widened until it holds a keyword, does not end on a name, and
matches nothing in the known-clean corpus (canonical examples and the
validated syntax Examples). `--` comments are not matched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012PkPkaYM12u8yqMKNmzBgT
Documents the microflow statements that start and manage workflows —
call workflow ($Wf = call workflow M.WF ($Obj);), get workflow data,
get workflows for, get workflow activity records, workflow operation,
open workflow, open user task, lock/unlock workflow — with examples that
pass `check --deprecations=error`. Cross-linked from microflow.call and
workflow.call-workflow.

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

SettleGeneratedSources (the final step of `mxcli new`) and docker
build's mxbuildCmd called cmd.Run() with no deadline, so a build that
stopped making progress — Gradle spinning behind a bad --java-home on
macOS (ChipCoV8 #6) — hung forever.

runWithWatchdog starts mxbuild in its own process group, kills the whole
tree when the build exceeds MXCLI_MXBUILD_TIMEOUT (default 15m) or prints
nothing for MXCLI_MXBUILD_STALL_TIMEOUT (default 5m), and returns an
error with the last output lines, the java home used and the variable
that raises the limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012PkPkaYM12u8yqMKNmzBgT
A false kill of a slow but healthy first build costs more than a late kill
of a hung one, and Gradle steps can print nothing for minutes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012PkPkaYM12u8yqMKNmzBgT
A column with no attribute, no content widgets and no ShowContentAs is
still an attribute column (showContentAs defaults to attribute), but it
was classified by whether an attribute was bound and fell to the default
kind, so its tooltip was written null where mx update-widgets writes an
empty Forms$ClientTemplate — CE0463. Classify by the effective
showContentAs (authored, else the widget default).

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

`column (Attribute: FieldService.ServiceRequest_LithoSystem/SerialNumber)`
failed check with MDL-WIDGET34 + MDL-WIDGET42 while the bare
`ServiceRequest_LithoSystem/SerialNumber` passed. attributePathV3 took only
plain identifiers between slashes, so the qualified form fell through to the
generic `keyword: expression` alternative and parsed as a division. Each hop
before the final segment may now be `Module.Assoc`; the final segment stays
bare. The builder already resolved qualified hops; describe still prints the
short form.

The MDL-WIDGET34 refusal for a value that is not an attribute path now
quotes the source text (not `&{...}`), names an unnamed widget by its
caption, suggests `Assoc/Attr or Module.Assoc/Attr` for a value with a `/`,
and stops advising a data view when the widget already sits in one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012PkPkaYM12u8yqMKNmzBgT
The System module's access rules are not in the .mpr, so nothing in mxcli
could say whether a role may read a System entity. Measure them from the
System domain model mxbuild writes into deployment/model/model.mdp
(Mendix 11.15.0, Production security), translating each rule's stored user
roles to System.User / System.Administrator.

Notable: System.User is [id = '[%CurrentUser%]'] for every role,
Administrator included; an Administrator's user list comes from a
specialization such as Administration.Account. System.FileDocument and
System.Image have no rule at all.

TestSystemReadAccessMatchesMDP re-checks the table against a build given
with -mdp, and fails when a row is changed (control run on System.Language).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012PkPkaYM12u8yqMKNmzBgT
A combo box over System.User filtered by role rendered empty for a non-admin
role, and check, mxbuild and the runtime log were all clean (ChipCoV9).

SEC010 takes every list-like data widget on a page (list view, data grid,
template grid, reference selectors, pluggable data grid / gallery / combo
box) and asks, per user role that may open the page, whether any of its
module roles has a read rule on the entity, a generalization or a
specialization. No rule -> warning ("renders empty for them"); only
XPath-constrained rules -> info; System.User row-scoped -> warning that it
lists only the current user. System entities use the measured
meta.SystemReadAccess table. Only at Production: Prototype is
CheckFormsAndMicroflows, which does not enforce entity access.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012PkPkaYM12u8yqMKNmzBgT
`check -p` and exec's preflight skipped any `create or modify` of a stored
flow whose declared body did not build against the stored project. A body
calling a flow an earlier statement of the same script creates is exactly
that case, so a change exec refuses (MDL-V1-REBUILD, "$X is still used by")
checked clean, and exec wrote the earlier statements before refusing it.

The verdict build now resolves calls to flows the script itself declares
(an overlay of the script's create [or modify] microflow|nanoflow
signatures, generalising the self-call resolution), so check and the
preflight refuse it before anything is written. A flow change that is
still not predictable is reported with what it depends on instead of the
unconditional "no flow change exec would refuse".

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

feat(test): --app-port, --admin-port and --serve-port for test --local
…bootstrap

fix(init): app-named docs, release-pinned bootstrap, visible DB-provisioning failure
…e-0emnau-test-errors

# Conflicts:
#	.claude/skills/mendix/test-microflows/SKILL.md
…x-help

fix(syntax): stop teaching deprecated spellings, and test it
…ualified-path

fix(pages): accept Module.Assoc/Attr as a widget attribute path
ako and others added 9 commits October 12, 2026 00:48
…verdict

fix(check): predict flow rewrites that call a flow the same script creates
…unreadable

feat(lint): SEC010 — a page lists an entity a role that opens it cannot read
…-java

fix(docker): macOS JDK for the project's major, never /usr; bound one-shot mxbuild runs
…eader

fix(pages): Data grid 2 columns with no caption write the empty templates update-widgets writes
…errors

fix(test): carry the raised error message into the FAIL verdict
feat: domain model diagrams as SVG, shown in the companion
… it grants

A list over System.User for a non-admin role gets every row through
Administration.Account's rule, which the real 11.15 Administration module
grants Administration.User unconstrained — but that rule grants only
Account's own members (FullName, Email), so System.User.Name comes out
blank on every row but the user's own. SEC010 counted any entity-level read
on a specialization as reading the generalization and stayed silent on the
case it was written for (ChipCoV9 FINDINGS, re-checked on main).

A specialization's rule now raises the level only when its MEMBER_READ rows
include a member declared by the listed entity or one of its
generalizations; a rule with no member rows is taken at its entity-level
word as before. System.User's own rule also opens the users whose roles a
user role may manage, so Manage All Roles reads every name.

Measured on a scratch copy of ChipCoV9 (11.15, run --local, playwright):
ServiceManager ["", "", "demo_manager", ""]; with ManageableRoles
(FieldEngineer) demo_engineer's name appears too; Administrator sees all.
Lint on that copy now flags ServiceManager only.

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

fix(lint): SEC010 counts a specialization's rule only for the members it grants
@github-actions

Copy link
Copy Markdown

AI Code Review

Review Summary

This PR synchronizes 25 commits from ako/mxcli:main to mendixlabs/mxcli:main, introducing several new features and fixes:

  • Domain model SVG visualization (describe --format svg domainmodel)
  • SEC010 lint rule for unreadable data in widgets
  • mxbuild watchdog for one-shot builds
  • Test port improvements (mxcli test --local)
  • Bootstrap/init fixes (Java resolution, DB provisioning, script defaults)
  • Flow verdict enhancements
  • Syntax deprecation testing
  • Page widget fixes (DataGrid 2)

Critical Issues

None found.

Moderate Issues

None found.

Minor Issues

  1. Inconsistent error handling in cmd_domainmodel_svg.go:

    • The renderDomainModelSVG function returns an empty SVG string for modules with no entities, but doesn't handle potential errors from textPx calculations (though none exist currently). This is low-risk as the function is pure.
  2. Magic numbers in SVG generation:

    • Constants like svgMaxSpread = 2.5 and svgMaxNameLen = 60 lack explanation in comments. While reasonable, adding brief rationale would improve maintainability.
  3. Test complexity in validate_flow_verdicts_script_flows_pedapp_test.go:

    • The test creates a complex script with multiple flows to verify verdict predictions. While thorough, it could benefit from clearer comments explaining the test scenario.

What Looks Good

  • Full-stack consistency: The domain model SVG feature properly uses existing domain model AST/types without requiring new grammar/AST nodes (as it's a CLI command, not MDL syntax).
  • Thorough test coverage: All major additions have corresponding tests (SVG generation, SEC010 rule, mxbuild watchdog, test ports, flow verdicts).
  • Security focus: SEC010 rule correctly handles System module access rules via meta.SystemReadAccess and implements proper specialization inheritance logic.
  • Robustness improvements: mxbuild watchdog addresses historical hanging issues with proper timeout/stall detection and process tree killing.
  • Documentation: Bug findings are properly recorded in .claude/skills/fix-issue/findings/ following project conventions.
  • Deprecation handling: New syntax deprecation test ensures mxcli syntax output doesn't teach deprecated spellings.
  • Atomic commits: Individual commits in the sync appear focused (e.g., separate commits for SVG logic, tests, CLI wiring).

Recommendation

Approve. The PR introduces valuable features with proper testing and follows project conventions. The minor issues noted are trivial and don't block merging. The synchronization approach is acceptable given the fork's maintenance workflow.

Note: The VS Code extension companion feature mentioned in the PR body (hover tooltips for SVG) appears to be in the vscode-mdl/ directory, which wasn't shown in the diff (likely excluded as noise per instructions). This is expected as the PR focuses on CLI/backend changes.


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

@ako
ako merged commit aa8c25b into mendixlabs:main Oct 12, 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.

2 participants