Published OData services could not build from MDL; JAR dependencies never resolved; init/settings/grammar fixes (formula1 findings) - #110
Merged
Conversation
A published OData service created purely from MDL passed `mxcli check` and then failed the build on two counts, neither of which the author could see coming. CE0729 "The service name should not be empty": `Name` (the document) and `ServiceName` (the name in the OData metadata document) are different properties, and CREATE set only the first. The consumed path has defaulted the same field to the document name for CE0339 all along. CE7375 "Attribute ID ... must be published and be the key when associations are exposed as an associated object id", firing even with no associations exposed: PublishAssociations defaults to false, which is object-id mode, and Mendix only allows that when the system ID is published as the key — while MDL's `expose (Attr (KEY))` publishes an ordinary attribute. The finding framed the second as a non-persistable-entity problem. It is not: measured on 11.12.1, the identical service over a PERSISTENT entity with a unique key builds 0 errors with true and CE7375 with false. The default broke every published service; non-persistable is only where no workaround exists, because publishing the ID of a non-persistable entity is forbidden. So default an unspecified PublishAssociations to true. That is not a preference, it is the only value that can build from the MDL people write. An explicit false is still honoured (tracked separately from "absent" so it survives), warned about when a published entity is non-persistable, and `create or modify` no longer flips a stored value the script never mentioned. Nothing in-repo caught either default: doctype-tests/10-odata-examples.mdl sets both properties explicitly. Both bug-test scripts now build 0 errors on 11.12.1 with no workarounds — no ServiceName, no follow-up ALTER. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
The grammar accepts any `name: value` pair in an OData property list and the visitor's switch had no default, so `ReadMicroflow:` or `ServiceNam:` parsed cleanly, checked cleanly, executed "successfully" — and left the model without the property. The ALTER path has always answered "unknown OData service property"; CREATE, PUBLISH ENTITY, the OData client and the external entity had nothing. The name is lost in the visitor, so the visitor is where it has to be recorded: each switch grows a default that appends to UnknownProperties, and a check-time validator reports them as MDL-ODATA01 before anything is written. The message guesses the intended property (prefix/substring, then one edit) rather than only listing the known ones — with a list alone the reader is still diffing two spellings by eye. One correction to the report: `Pagesize:` is not among the casualties. The visitor lowercases before matching, so casing is never a typo. A test pins that, so the rule cannot start flagging it later. No false positives across every script in mdl-examples. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
The three OData query options were written as literal true in the BSON writer, with no MDL above them. Countable is not a cosmetic default: it forces every read-microflow-backed resource to declare a System.ODataResponse parameter and compute a count, and over a full CSV scan that count is the expensive part of the request. They are now publish-entity properties. Tri-state is load-bearing — they default to true, so "unset" and "false" cannot share a representation or every existing script would quietly turn them off. Unset stays nil, the writer resolves nil to true, and the reader maps a stored true back to nil so DESCRIBE prints what the author wrote rather than three defaults on every resource. Verified on 11.12.1: `Countable: No` with a read microflow that takes no $Response parameter builds 0 errors — the combination that could not be expressed before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Three slips in one emit block, each of which broke the DESCRIBE-roundtrip property the review checklist asks for: - `ReadMode: CallMicroflow:Module.Read` is the backend's storage spelling and matches no MDL value. It now prints `microflow Module.Read`, the form the author wrote. - `expose (...)` takes a bare member name; the stored name is fully qualified, so `Module.Entity.Attr` was emitted and did not parse. `IsPartOfKey` becomes `KEY` for the same reason — both parse, but only one is documented. - `as '<name>'` printed the entity TYPE's exposed name where the entity SET's belongs. That one is invisible until the two differ (Studio Pro exposes the type singular and the set plural), and then a describe -> exec cycle silently renames the set the $metadata serves. Proved by round trip rather than by eye: describe -> check parses, then drop the service, exec the describe output, describe again — byte-identical, and mxbuild reports 0 errors on the rebuilt model. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…ptions `ReadMode: microflow Module.MF` has worked through the whole pipeline — grammar, visitor, AST, BSON — and was documented nowhere, so the only way to find it was to read the Go source. Without it, "publish persistent entities" is the only shape the docs describe, which sends anyone with data outside Mendix down a materialise-into-the-database path they do not need. The syntax topic now carries ReadMode/InsertMode/UpdateMode/DeleteMode in their microflow form, ServiceName and PublishAssociations with their defaults, and the three query options. The skill gains a worked non-persistable example — read microflow, no copy of the data, no refresh job — with the two things that bite first: the $Response parameter Countable requires, and why PublishAssociations must stay at its default there. The skill's existing example also carried `PublishAssociations: No`, which cannot build: object-id mode requires the system ID as the published key, and that example publishes an ordinary attribute. Removed, with the reason. The new example was executed against a real .mpr and builds 0 errors on 11.12.1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
The skill's table listed `Redshift` and `SQLServer`. Neither is in Studio Pro's
connector picker on any version available here — read out of the shipped bundle
at modeler/ide-client/database-connector-editor/, identical on 11.10.0, 11.12.1
and (per the report) 11.13.0: MSSQL, MySQL, Oracle, PostgreSQL, Snowflake, BYOD.
mxcli writes the type string through unchanged and mxbuild does not validate it
either — `type 'Redshift'` builds 0 errors — so a wrong value hides behind a
green build and only shows up as a connection that does not connect. `check` now
says so. A warning rather than an error: the set is version-specific and mxcli
cannot prove a string wrong on a version it has not seen.
The table was also missing the entry that matters most. `BYOD` ("Other") forces
connection-string configuration and skips the driver-presence check, which makes
any JDBC driver Mendix ships no picker entry for usable by dropping the JAR in
userlib/. That is what the finding needed for DuckDB, and it read as impossible.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Run from a solution root — one folder per app, no .mpr at the root — `mxcli init` reported success and wrote tooling pointing at `project.mpr`, a file that does not exist. The hardcoded default made a missing project look like a found one. It now looks one level down and lets the candidate count decide: none, warn that the generated paths are placeholders; exactly one, say which project it is and initialise that directory; more than one, refuse, list them, and print the command naming a specific app. A solution repo should not get one app's tooling by coin flip. One level only. A Mendix app keeps its .mpr at its own root, so walking deeper would start finding deployment copies and backups. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
`alter settings configuration 'Default' …` is the write form; the read form was a parse error. `describe settings configuration '<name>'` now parses and prints that one configuration as re-executable MDL, naming the configurations that do exist when the name is wrong. Where MDL has `alter X <selector>`, reaching for `describe X <selector>` and getting a parse error teaches the wrong lesson. `show settings configurations` also gains ApplicationRootUrl. It decides the host the app answers on, so "did my root URL land?" is the obvious question to ask that command, and the summary could not answer it — `describe settings | grep` was the only way. An empty DatabaseUrl is skipped too, rather than rendering as a bare comma that reads like a bug in the reader. Both dumps go through one emit helper, so the whole-settings output and the single-configuration output cannot drift apart. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
`make build` needs an `antlr4` launcher, and the error when it is missing said only "install ANTLR4" — while the version is load-bearing. CI pins antlr4-tools 0.2.2 with generator 4.13.2 against the 4.13.1 runtime in go.mod, and a generator/runtime mismatch is a classic ANTLR failure mode that surfaces as a generated parser that will not compile. That pin lived only in the workflow YAML, where nobody hits it. The pin now lives in mdl/grammar/Makefile, the error message quotes it, and `make -C mdl/grammar bootstrap` does the pip install. `generate` also notes when ANTLR4_TOOLS_ANTLR_VERSION is unset, since the default is whatever antlr4-tools last downloaded. The README build-from-source recipe gains the bootstrap step and says what the build actually needs — network and a JVM, not only Go. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Step 6 was documented as "downloads the correct mxcli binary", and on Linux it does not download: it hard-links the mxcli you ran into the project, sharing the inode. That is the right behaviour for a from-source build — the app folder gets your binary, not the nightly — but the help text described the opposite, which is what led one report to delete the linked binary before moving files around. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
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.
Works through the findings from building an F1 data app: a DuckDB-backed, non-persistable entity published over OData v4. Everything was reproduced against real MxBuild before being fixed, and re-verified after (
mx check→ 0 errors on Mendix 11.12.1 — 11.13.0 was not cached here, so version numbers below are 11.12.1 unless stated).Two of the reported items did not survive checking; those are called out rather than quietly "fixed".
The headline: every published OData service from MDL was unbuildable
Two defaults
CREATE ODATA SERVICEnever set, each fatal on its own, neither visible tomxcli check:CE0729 "The service name should not be empty".
Name(the document) andServiceName(the name in the OData metadata document) are different properties, and CREATE set only the first. The consumed path had defaulted the same field to the document name for CE0339 all along; the published path never got the line. An explicitServiceName:still wins, andcreate or modifyheals an empty one.CE7375 "Attribute ID … must be published and be the key when associations are exposed as an associated object id" — firing with no associations exposed at all.
The finding framed this as a non-persistable-entity problem. It is not:
PublishAssociationsmx checkfalsemeans "associations as an associated object id", which Mendix only allows when the systemIDis the published key — while MDL'sexpose (Attr (KEY))publishes an ordinary attribute. So the default broke every published service; non-persistable is merely where no workaround exists.Defaulting to
trueis not a preference, it is the only value that can build from the MDL people write. Tri-state so an explicitfalsesurvives, andcreate or modifyno longer flips a stored value the script never mentioned.Nothing in-repo caught either default:
doctype-tests/10-odata-examples.mdlsets both properties explicitly.The other silent one: a declared JAR dependency never reached the classpath
ALTER MODULE X ADD JAR DEPENDENCY (…)wrote the coordinate,list jar dependenciesreported it, the build went green — and the runtime threwNo JDBC driver found in app for URL.The write was never wrong. Declaring and resolving are separate steps:
mx sync-java-dependencies <project.mpr>downloads intovendorlib/, Studio Pro runs it when you edit Module Settings, and nothing ran it headless. Measured on 11.12.1: a fullmxbuild --target=deployemits abuild.gradlewith no dependencies block and downloads nothing; the sync command then fetches the jar.That answers the open question in the report — neither "mxbuild skips Maven resolution" nor "mxcli writes where MxBuild cannot read". (Found by
strings mx.dll | grep -i dependencsurfacingISyncJavaDependenciesRunner, thenmx --help.)Wired at three levels so it cannot stay silent:
vendorlib/;mxcli sync-java-depsdoes the resolution on demand, with--checkexiting non-zero as a build gate;run --localvendors anything missing before boot, so the warm loop works from a fresh clone.Resolution needs network, so each call site is best-effort and names the command to retry. Falling back to another Mendix version's
mxis now stated rather than silent — a mismatch otherwise surfaces as an mpr-format complaint that reads like a corrupt project.Corrections to the report
PublishAssociationsis accepted onCREATEtoday (visitor_odata.go— the grammar takes a generic property list). A one-shot script could always express it; what was missing was documentation. The real defect was the default.Pagesize:is not silently dropped. The visitor lowercases before matching, so casing is never a typo. A test pins that so the new rule cannot start flagging it.Check-time gaps the build does not cover
Silently discarded OData properties (MDL-ODATA01). The grammar accepts any
name: valuepair and the visitor's switch had nodefault, soReadMicroflow:orServiceNam:parsed, checked, executed "successfully" — and left the model without the property. The name is lost in the visitor, so that is where it is recorded; a check-time validator reports it before anything is written, guessing the intended property rather than only listing the known ones.Unreal database-connection types (MDL-DB01). The skill's table listed
RedshiftandSQLServer; neither is in Studio Pro's picker, read out of the shipped bundle and identical on 11.10.0, 11.12.1 and 11.13.0: MSSQL, MySQL, Oracle, PostgreSQL, Snowflake, BYOD. mxcli writes the string through and mxbuild does not validate it either —type 'Redshift'builds 0 errors and simply does not connect. A warning rather than an error: the set is version-specific.BYOD("Other") was the missing entry that mattered — it skips the driver-presence check, which is what makes an unsupported JDBC driver usable. Now confirmed working against a live runtime, so the docs were the only thing in the way.Query options and the DESCRIBE round trip
Countable/SkipSupported/TopSupportedwere literaltruein the BSON writer with no MDL above them. Countable is not cosmetic: it forces every read-microflow-backed resource to declare aSystem.ODataResponseparameter and compute a count. Now publish-entity properties, tri-state because they default to true. Verified:Countable: Nowith a read microflow that takes no parameters builds 0 errors.DESCRIBE emitted MDL it could not parse. Three slips, plus a fourth not in the report: the stored
CallMicroflow:spelling, the entity type's exposed name where the entity set's belongs (silently renaming the set on a re-exec), and fully-qualified member names in anexpose (...)clause that takes bare ones. Proved by round trip: describe → check parses → drop → exec the output → describe again → byte-identical, and mxbuild reports 0 errors on the rebuilt model.MDL papercuts
$Total = 5;now parses. It failed with "no viable alternative at input '$Total=5'" whileDECLARE $Total Integer = 0;worked — and so did$X = HEAD($List),$X = create M.E (…)and$X = execute database query …. Assignment existed only as a prefix on activity statements plus aSET $Var = …statement, so a plain value needed a keyword nothing suggested.SETis now optional; both spellings produce the same AST.Regressions were checked with a control binary rather than by reading the grammar: stash the
.g4, regenerate, buildbin/mxcli-control, sweep every script inmdl-exampleswith both. Thirteen fail — the same thirteen, all pre-existing.mxcli test tests/ -p app/App.mprno longer fails for atests/next to the.mpr. The fallback applies only when the CWD-relative path does not exist, so atests/in both places still resolves to the one you are standing in.Smaller ones
mxcli initstopped inventing a project. From a solution root it reported success and wrote tooling pointing atproject.mpr, a file that does not exist. It now looks one level down and lets the candidate count decide: none → warn; one → name it and initialise that directory; two or more → refuse and print the command naming a specific app.describe settings configuration '<name>'parses (the read form of an existing write statement), andshow settings configurationscarriesApplicationRootUrl.make -C mdl/grammar bootstrapdoes the install, and the README says the build needs network and a JVM, not only Go.mxcli newstep 6 says "links" — on Linux it hard-links the binary you ran rather than downloading the nightly.ReadMode: microflow …,ServiceNameand the query options inmxcli syntax odata publish, with a worked non-persistable example. The skill's own example carriedPublishAssociations: No, which cannot build; removed with the reason. Added the twodynamicquery rules (every declared parameter still needs a value; a{param}placeholder concatenates into a path).Not done
pkillis correct behaviour — it is a daemon, reused across builds.Verification
mdl-examples/bug-tests/execute against a real.mprand build 0 errors with no workarounds.MDL-ODATA01andMDL-DB01checked against every script inmdl-examplesfor false positives: none.run --local --setup→ jar invendorlib/.mainafter mxcli-todo findings: nine fixes from bootstrapping an app (check-parity, first-build cleanliness, theming, diagnostics) #107/Fix silent drops of conditional expressions with keyword-named functions #108/Add test endpoint for warm test re-runs without restart #109; grammar regenerated,go build ./...clean, fullgo test ./...green..claude/skills/fix-issue.mdfor each fix.🤖 Generated with Claude Code
https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4