Two lint rules a real project asked for, and a describe → check round trip that was failing on 6 of 14 workflows - #1066
Merged
Merged
Conversation
…s (CONV018)
Studio Pro lets a module hold folders and every Mendix style guide expects
them once a module is more than a handful of documents, but nothing in mxcli
reported their absence: a project with 200 documents loose in one flat module
scored clean across all three rule sets.
CONV018 reports a module only when BOTH halves hold:
1. more than max_root_documents (default 20) documents sit directly in the
module root, AND
2. not one document in the module is in a folder.
The second half is what keeps it from nagging. A module that has started to
organise itself — even one folder — is never reported, however much is still
at its root: the team has evidently made a choice about where things go, and
a linter guessing at the rest is noise. The first half exempts modules that
are small rather than disorganised.
Only kinds Studio Pro actually lets you file in a folder are counted. An
association belongs to the domain model and an external entity to a consumed
service, so counting them would report a module as unorganised on the strength
of elements nobody can move. That list is an allow-list in the rule, where it
is visible and editable, and its polarity is deliberate: a document type
missing from it is undercounted, so the rule stays quiet rather than inventing
a violation.
The rule stands on a new documents() Starlark builtin — every element of the
App Explorer tree as (kind, name, qualified_name, module_name, folder), read
from the catalog's `objects` view so a new document type is covered without a
second list to keep in step. It is the companion to documentable_elements(),
which projects only what can carry documentation and therefore omits
microflows and Java actions — the two kinds that fill up an unorganised
module. No per-kind builtin exposed `folder` uniformly, which is why the rule
could not be written before.
Verified live, not only in unit tests: a 25-microflow flat module reports, and
goes silent the moment one MOVE ... TO FOLDER lands; max_root_documents read
from .claude/lint-config.yaml silences it at 40 and brings it back at 5.
Tests carry their controls. The two silence assertions are paired with
sibling tests that fire on the same fixture, the Marketplace exclusion is
followed by the same module without the marker (which must report), and the
non-foldered-kind test is followed by 40 rows of a kind that can be foldered
(which must report). Stubbing check() to return [] fails four of the six.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…019) A Mendix page is reachable at /p/<url> only if it has been given a URL. Without one it exists solely at the end of a click path: it cannot be bookmarked, linked to from an email, reopened after a browser refresh, or captured with `mxcli run --local --screenshot-url`, so verifying one screen means driving a browser through login and the menu (ako/CapTrackV4 FINDINGS 014, R4). The rule reports only the pages a navigation profile ROUTES TO — the profile home page, role home pages, and menu item targets. That narrowing is the whole design, and it is measured rather than assumed: on a blank Mendix 11.12.1 app 0 of 16 pages carry a URL, Mendix's own Home page and every Administration and FeedbackModule page included. Reporting every page without one would warn about every page of every project from the day it is created, which is noise rather than a finding. The screens a profile routes to are different: they are what a user lands on, bookmarks and shares. A page reached only from a button inside another screen is never reported. Also never reported, each for its own reason: the login and not-found pages (the platform routes to those itself, so a URL buys nothing), microflow-valued menu items and home pages (the microflow decides what opens, so there is no page to be addressable), and targets in System or Marketplace modules. A page routed to several ways is one finding naming every route, not one per route. Carried by a new navigation_targets() Starlark builtin — (profile, kind, role, caption, page) for every routed-to page, where kind is home, role_home or menu. Navigation was reachable only from the Go rules through the reader's GetNavigation, so no Starlark rule could ask which pages a user can actually reach. Verified live, not only in unit tests: on a blank app it reports Home_Web and nothing else, naming both routes that reach it; two pages created in one statement — one with `Url:`, one without — separate exactly as intended, the linked one silent and the unlinked one reported. Tests carry their controls. The URL-set test asserts the other two still report, the Marketplace test asserts the three user pages still do, and the two projection tests assert the surviving targets rather than only the absent ones. Stubbing check() to return [] fails four of the six. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
mxcli can create an offline navigation profile and can read every row of its sync configuration, but cannot write one. The result is a profile that builds, routes and installs as a PWA and shows an empty app, because a Mendix offline profile downloads nothing until each entity is given a synchronization mode. DESCRIBE NAVIGATION already states the gap in its own output — the rows come back commented, marked "not yet modifiable" — which also makes describe -> exec lossy for any project using offline sync, the same round-trip failure that preceded the layout and menu-icon work. Checked first, because it decides whether this is a feature or a rescue: an existing configuration SURVIVES a rewrite. Both engines patch the stored profile rather than rebuilding it, and neither write path touches OfflineEntityConfigs. This is a clean gap, not the data loss that `create or modify entity` was inflicting on access rules. Two hazards the proposal is mostly about, both with a precedent from this week: The read is lossy. gen declares six properties on the config element and mdl/types keeps three, discarding DownloadMode, ShouldDownload and CompatibilityMode — the last being the column with warning triangles in the report's screenshot. A writer built from the three the semantic model carries would drop the others, which is the access-rule defect again; so phase 1 is carrying all six on READ, before anything writes. The sync mode is an enum whose captions are not its keys. The metamodel declares All / Constrained / Never / None / NoneAndPreserveData / Online while Studio Pro shows "Online", "All Objects", "By XPath" — so the mapping cannot be read off the UI at all. That is mendixlabs#1035 exactly, where a caption reached gallery.def.json and mxbuild rejected every gallery with CE0463, and the guard that closed it is the one to copy. Syntax is a per-entity SYNC block matching the MENU block's shape, with WHERE implying Constrained so the invalid pairings are unspellable rather than merely diagnosable. Honest about what is not known: no project on this machine has a populated offline entity config — an earlier scan claiming five was matching the key name that every profile carries, not the element. The shape has to come from a real document, and §5 lists the four things it settles, including where the throw-on-reject setting lives, which is on neither gen's profile nor the metamodel's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`describe workflow` emitted MDL that `mxcli check` refused: 6 of the 14
workflows in the 9 demo apps in mx-test-projects/ failed describe -> check.
Two independent defects behind one symptom.
MDL-WF03 refused the enumeration value Studio Pro actually stores. The rule
required a bare identifier, but every Workflows$EnumerationValueConditionOutcome
in the corpus holds the qualified form Module.Enum.Value (7 of 7 non-empty), so
the describer emitted what was stored and the checker rejected mxcli's own
output. The rule exists to catch free text like 'Confirmed closed'; rejecting
the dot was collateral, and spaces are still refused. The stored document is
what settled which side to change — emitting the last segment instead would
have quieted check and written a document unlike every real one.
MDL could not name the activities a `jump to` targets. Mendix stores
JumpToActivity.TargetActivity as an activity NAME string, not a pointer, and
Studio Pro names activities by type and ordinal (decision1, split1,
callMicroflow1, waitForNotification1) with no relation to the caption. MDL had
a name slot only on `user task`; every other builder did act.Name = act.Caption,
so the stored name had nowhere to be emitted to, two decisions sharing a caption
collided on one name, and a `jump to decision1` resolved to nothing. Reaching
MxBuild it became a jump to itself and surfaced as CE6681 ("not possible to jump
to end activities or jump-to activities") — an error naming a different fault.
This was not a describer bug: the grammar had no name slot, so the fix runs
grammar -> AST -> visitor -> builder -> describer.
`decision`, `parallel split`, `wait for timer` and `wait for notification` now
take an optional name; `call microflow` / `call workflow` take `AS <name>`.
Without one the caption-derived name is unchanged, so existing scripts are
unaffected, and DESCRIBE emits a name only where it is not derivable.
Verified with a control: the pre-fix binary built from the parent commit
reproduces 6/14 failing where the fixed one is 0/14. The round-tripped workflow
was executed into FactoryManagement(AgenticEnterpriseEdition) and read back —
decision1..3, split1, callMicroflow1..6 and waitForNotification1..2 all land and
both jump targets resolve — on top of mx check at 0 errors against an untouched
baseline (mxbuild 11.10.0). mx check alone would not have shown it: a workflow
that jumps to itself is perfectly valid.
Closes #408
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat(lint): two rules for things the model shows and nothing reported (CONV018, CONV019)
docs(proposal): offline synchronization configuration
fix(workflow): make describe output re-executable (#408)
`ALTER ENTITY M.Gen ADD ATTRIBUTE …`, where a specialization of Gen also has an access rule, produced CE0066 "Entity access is out of date" — and `UPDATE SECURITY`, project-wide or scoped, reported "All entity access rules are up to date" and changed nothing (mendixlabs#1047, reported against 0.21.0 on a confirmed MPR v2 project). ReconcileMemberAccesses computes the same-module ancestor set and then used it ONLY for the association pass. The attribute pass beside it walked the entity's own attributes, so a specialization's expected member set never contained the members it inherits: nothing looked missing, nothing was added, and `modified` stayed 0 — which the command prints as "up to date". A false success is worse than an error, because it ends the investigation: the reporter reasonably concluded the command was broken rather than the model. Inherited members are now included, each qualified against the entity that DECLARES it (M.Gen.AfterSpec, not M.Spec.AfterSpec — that is CE1613), with a child attribute shadowing an ancestor's of the same name, matching the executor's own member walk. An ancestor in another module is still neither added nor pruned; its domain model is not loaded here, so the existing entries are carried through by the preserve branch. Stale inherited entries are likewise still preserved — that is the opposite direction from this defect, and the report's own control says "-0 removed". Both engines had it in the same shape and both are fixed. A fix in one of these parallel writers stays latent in the other until something switches engines. Two things fixed in passing, in code this change already touches: the legacy add-loop iterated a map, so the order of new entries varied between runs; and legacy keyed coverage by bare attribute name while preserving any reference not qualified against this entity, which would have preserved AND re-added the same member once inherited ones were expected. The duplicate only appears on a second reconcile, so the regression test runs it twice. Measured on 11.12.1, both engines: the reported repro now checks clean, and a project broken by the PRE-FIX binary is repaired by the fixed one — "Reconciled 1 access rule(s) in module ProbeSecond", mx check 1 -> 0, on the project-wide and the scoped form alike. That is also the first end-to-end proof of the repair path, which an earlier fix recorded as unproven: once the write path reconciles, no MDL script can produce a CE0066 to repair. Reverting either engine's attribute walk to own-attributes-only fails its test with the reported symptom. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
fix(security): reconcile the members a specialization inherits (mendixlabs#1047)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four commits: two new lint rules, one round-trip fix, one proposal.
describe workflowemitted MDL thatmxcli checkrefused6 of the 14 workflows across the 9 demo apps in
mx-test-projects/failed describe → check. Two independent defects behind one symptom.MDL-WF03 refused the enumeration value Studio Pro actually stores. The rule required a bare identifier, but every
Workflows$EnumerationValueConditionOutcomein the corpus holds the qualifiedModule.Enum.Valueform — 7 of 7 non-empty — so the describer emitted what was stored and the checker rejected mxcli's own output. The rule exists to catch free text like'Confirmed closed'; rejecting the dot was collateral, and spaces are still refused. The stored document is what settled which side to change: emitting the last segment instead would have quietedcheckand written a document unlike every real one.MDL could not name the activities a
jump totargets. Mendix storesJumpToActivity.TargetActivityas an activity name string, not a pointer, and Studio Pro names activities by type and ordinal (decision1,split1,callMicroflow1) with no relation to the caption. MDL had a name slot only onuser task; every other builder didact.Name = act.Caption, so the stored name had nowhere to be emitted to, two decisions sharing a caption collided on one name, andjump to decision1resolved to nothing. Reaching MxBuild it became a jump to itself and surfaced asCE6681— "not possible to jump to end activities or jump-to activities" — an error naming a different fault.That was not a describer bug, so the fix runs the full width: grammar → AST → visitor → builder → describer.
decision,parallel split,wait for timerandwait for notificationnow take an optional name;call microflow/call workflowtakeAS <name>. Without one the caption-derived name is unchanged, so existing scripts are unaffected.Two lint rules, both narrowed by measurement
CONV018 — a module never organised into folders. A project with 200 documents loose in one flat module scored clean across all three rule sets. The rule fires only when both halves hold: more than
max_root_documents(default 20) sit directly in the module root, and not one document in the module is in a folder.The second half is what stops it nagging. A module that has started to organise itself — even one folder — is never reported however much remains at its root: the team has evidently made a choice, and a linter guessing at the rest is noise.
Only kinds Studio Pro actually lets you file in a folder are counted, as an allow-list rather than a deny-list. An association belongs to the domain model and an external entity to a consumed service, so counting them would report a module as unorganised on the strength of elements nobody can move. The polarity is deliberate: a document type missing from the list is undercounted, so the rule stays quiet rather than inventing a violation.
CONV019 — a navigation screen that cannot be linked to. A Mendix page is reachable at
/p/<url>only if it has been given a URL. Without one it exists solely at the end of a click path: no bookmark, no link from an email, no reopen after refresh, and nomxcli run --local --screenshot-url— so verifying one screen means driving a browser through login and the menu.The rule reports only the pages a navigation profile routes to — profile home, role homes, menu targets. That narrowing is the whole design, and it is measured rather than assumed: on a blank Mendix 11.12.1 app, 0 of 16 pages carry a URL, Mendix's own Home page included. Reporting every page without one would warn about every page of every project from the day it is created.
Never reported, each for its own reason: login and not-found pages (the platform routes to those itself), microflow-valued menu items and home pages (the microflow decides what opens, so there is no page to be addressable), and System or Marketplace targets. A page routed to several ways is one finding naming every route.
Both rules needed new Starlark builtins, and those are the reusable part:
documents()exposes every element of the App Explorer tree as(kind, name, qualified_name, module_name, folder)read from the catalog'sobjectsview — so a new document type is covered for free — andnavigation_targets()gives(profile, kind, role, caption, page)for every routed-to page. Navigation was previously reachable only from the Go rules, so no Starlark rule could ask which pages a user can actually reach.Proposal
Offline synchronization configuration. mxcli can create an offline navigation profile and read every row of its sync config, but cannot write one — so the profile builds, routes, installs as a PWA and shows an empty app. Two hazards documented before any code: the read is already lossy (
gendeclares six properties on the config element, the semantic model keeps three), and the sync mode is an enum whose Studio Pro captions are not its keys — the same shape as theCE0463gallery defect. Draft; no code