Sync ako/mxcli: microflow rewrite carries, run --local resolution, version gating, catalog entry-point edges - #1133
Merged
Merged
Conversation
Named datasources for multi-source pluggable widgets (mendixlabs#1109)
…#1120) A microflow's URL (Mendix 10.6+) — Studio Pro's "URL" field, e.g. `item/{Key}` — was deleted by any CREATE OR MODIFY MICROFLOW, including one that edited only the body. The value had no path across a rewrite at any of the three layers: `microflowToGen` wrote `SetUrl("")` and `SetUrlSearchParametersQualifiedNames(nil)` unconditionally, `microflowFromGen` never read either back, and the semantic microflow had no field to hold them. Carry all three, and seed the executor's rewrite from the stored microflow, exactly as AllowConcurrentExecution / MarkAsUsed / ApplyEntityAccess already are. This one had nothing behind it: a microflow without a URL is a valid microflow, so `mxcli check`, `mx check` and mxbuild all reported success before and after, and the loss was visible only in Studio Pro. There is no refusal to fall back on — preserving is the whole remedy. DESCRIBE now emits the URL as a `-- URL:` comment. It is not re-executable MDL because there is nothing to execute, but a describe -> rename -> exec copy has nothing to preserve from, so the output says so instead of silently omitting it. Controls: reverting either half of the backend fix alone fails TestMicroflowRoundTrip_DeepLinkURL with the reported symptom (Url = ""), and neutralising the executor carry fails TestCreateOrModifyMicroflow_PreservesDeepLinkURL. Both directions are pinned — a microflow with no URL must not acquire one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ssUKiZ9ekBvzSNM5VCVoP
The gate list was stated in three places that each reach a different
reader, and they had drifted apart:
- the generated CLAUDE.md/AGENTS.md, re-read into every session, so it
is what an agent does by default without being asked;
- the bootstrap-app skill, which provisions the project and hands over;
- the bootstrap-prompt docs page, which a human reads before pasting
the seed prompt.
`mxcli test` appeared in the skill only as a ports aside ("avoid
8081/8091/6544") and on no gate list at all, so testing was reachable
only by a user asking for it by name — while `check`, repeated in 41
skills, ran almost every time. `docker check` was on the CLAUDE.md list
and in neither of the other two. The docs page's step numbering had also
gone stale ("provisioning step 6" for what is step 7).
Changes:
- projectGates is now one ordered list in Go, rendered into the
generated file, with `test` added between `docker check` and
`run --local`. The gates are stated as the definition of done.
- A "Finishing a change" section carries the bootstrap's durable
artifacts into the steady state: run the gates, `brain capture`
requirements as they arrive, append to FINDINGS.md, commit. Without
it those files are written once at bootstrap and quietly stop being
true. Generated file is 5,113 bytes, within the 6,000-byte budget.
- bootstrap-app gains a quality-baseline step (lint + the scored
report on the blank app, recorded in FINDINGS.md) — the only moment
those numbers mean "what the template ships with" — and states the
same gate list, with the rule that the first test is written with
the first microflow rather than later.
- The docs page mirrors the skill's steps, including the baseline, and
publishes the same gate list.
Held together by init_claudemd_gates_test.go: every gate must be a real
cobra command and must be named in all three. Controls: the skill test
fails against the pre-change skill (lint/report/docker check), and
dropping the `test` line from the docs page fails the docs test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HK2imrM8M5UbzhTvttb5YC
…he cache `run --local` and `test --local` resolve the mxbuild this host can execute (ResolveMxBuildForLocal, mendixlabs#916) and then passed it only to the web-client bundler. Both ServeOptions literals omitted MxBuildPath, so StartServe re-resolved from ~/.mxcli/mxbuild/ and, on a miss, took the newest cached version whatever it was — so a Mac with the matching Studio Pro installed built with a Linux download or with 11.14.0 against an 11.12.2 project. The half-populated cache directory that causes the miss is mxcli's own: ensureMxBuildRuntimeSibling creates ~/.mxcli/mxbuild/<ver>/ to hold the runtime symlink, and on macOS nothing puts a modeler/ beside it. So this is the guaranteed path for every macOS project whose version was not already downloaded, not a damaged-cache edge case. - both call sites construct options through one serveOptionsFor, so they cannot drift apart again - resolveServeMxBuild no longer substitutes a different version for a named one; it refuses up front, naming what the cache holds, instead of handing mxbuild a binary it rejects minutes later after a cold model load - test --local now resolves at all: it still called DownloadMxBuild directly, the call mendixlabs#916 removed from runlocal.go and left in localapp.go - the failure message no longer sends macOS users to `setup mxbuild`, which prints Studio Pro's path and caches nothing (mendixlabs#1124) Fixes mendixlabs#1122. Reduces mendixlabs#1124 to its guidance text. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019qk7hHmsPXfeEMAGphVA4H
`mxcli run --local --mxbuild-path …` answered "unknown flag", while the shipped run-local skill, runlocal.go's comment and two resolution error messages all tell users to pass it. On macOS it was the only advertised way out of a platform mismatch. The plumbing behind the flag already worked — LocalRunOptions.MxBuildPath is declared and ResolveMxBuildForLocal honours it — so only registration and the field assignment were missing. The regression test asserts the invariant rather than the instance: any --flag an mxbuild-resolution message tells users to pass must be a flag `run` accepts. Guidance naming an option the command rejects is worse than no guidance, because it reads as the user's mistake. Fixes mendixlabs#1125. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019qk7hHmsPXfeEMAGphVA4H
…1120 follow-up) A microflow's export level — Studio Pro's Hidden/API switch, which decides whether it is part of the module's public surface when the module is exported as a package — was pinned to "Hidden" by every rewrite. Same three-layer gap as the deep-link URL in the same function, and the same silence behind it: a hidden microflow is a valid microflow, so nothing downstream notices the module's API getting smaller. Found by the mechanical audit the URL fix prompted — grepping microflowToGen for the constants it writes. Unlike the URL, this one needs a DEFAULT as well as a carry. "" is not a member of MicroflowsExportLevel, and writing a value the metamodel does not declare is the unloadable-model hazard: mxbuild tolerates it, Studio Pro throws. A fresh microflow and a stored document that says nothing both get "Hidden" — what the line always wrote — so the common case is byte-identical. The precedent is json_write.go. Measured, and it is what shaped the fix: across three real marketplace modules (Business Events 3.12.0, External Database Connector 6.2.3 and 6.3.0) every document of every type stores "Hidden" — 3 of 3 microflows, 55 of 55 documents — all three exporting at module level "Source". So the hardcoded value was not wrong, it was a default masquerading as a constant. DESCRIBE therefore emits `-- Export level:` only when the value is not "Hidden", alongside the URL comment. Rules keep their hardcoded "Hidden" (2 of 2 reference rules, and a rule is not independently callable); the now-stale cross-reference in rule_write.go is corrected and says what would change that. Controls: pinning the writer back to "Hidden", stubbing the reader, and neutralising the executor carry each fail a different test with the reported symptom; stubbing the describe condition fails the output test, whose own control proves it stays quiet on defaults. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ssUKiZ9ekBvzSNM5VCVoP
`CREATE PAGE ... (Params: ...)` was refused on every Mendix 10 project, so no parameterised page could be authored from MDL there at all — and without one, `SHOW PAGE Mod.P ($X = $obj)` has nothing to bind to. The 11.0 floor was never measured. `pages.page_parameters`, `microflows.show_page_with_params` and `pages.page_variables` all matched the illustrative `show features` sample table in docs/11-proposals/PROPOSAL_version_aware_agent_support.md and nothing else. Measured against the Mendix Model SDK's own StructureVersionInfo records (mendixmodelsdk 4.115.0, src/gen/pages.js): Pages$PageParameter, Page.parameters 9.4.0 Pages$PageSettings.parameterMappings 9.7.0 Pages$LocalVariable 10.17.0 Pages$LocalVariable.defaultValue 10.20.0 Pages$PageParameter.isRequired 11.5.0 Pages$PageParameter.defaultValue 11.5.0 So the element is 9.4 and only its optional/default-value half is 11.5 — one floor cannot express that. The registry now gates the element and the writer gates the tail: pageParameterToGen emits IsRequired/DefaultValue only on 11.5+. MDL can express neither (every parameter it writes is required with no default), so below 11.5 they are two keys the project's metamodel does not declare — mxbuild accepts unknown properties, Studio Pro throws InvalidOperationException at MprProperty.cs. Lifting the gate without that guard would have traded an honest refusal for a page Studio Pro cannot open. The same split shows the bug was live on released 11.x too: 11.0–11.4 passed the old gate and got both 11.5-only keys written. That row is the regression control in the writer test. The executor gate itself stays — it is still correct for 9.0–9.3 — and the floors now carry a `notes:` naming the measurement source, so the next reader does not re-derive them from a proposal's sample output. Page variables are set to 10.20.0 rather than 10.17.0 because MDL always writes a default expression, and DefaultValue is the later of the two. Tests: sdk/versions/page_parameter_floor_test.go (each floor plus the version below it), mdl/backend/modelsdk/page_parameter_version_test.go. Both were run against the unfixed code as controls: the registry test reports `page_parameters at 10.24.25 = false` (the reported symptom) and the writer test reports both keys emitted at 10.24.25, 9.4.0, 11.4.0 and unknown. Fixes mendixlabs#1121 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016aHj6mJwKCZD7EX7wcD6jW
The catalog recorded each published REST operation's microflow in published_rest_operations_data.Microflow and emitted no `refs` edge to it, so a microflow whose only caller is an endpoint had zero inbound references. GRAPH_DEAD_ASSETS listed it, SHOW CALLERS OF answered "(no callers found)", SHOW REFERENCES TO and impact reported nothing, and lint rule QUAL004 said "is not called from anywhere. Remove if unused." On the reported model 92 of 93 operations name a microflow and all 92 were listed dead -- 15 percent of its dead-microflow list, aimed at the most exposed code in the app. A published REST operation is an entry point of the same shape as a scheduled event: the platform invokes it, so nothing in the model calls the microflow it runs. It now emits a `publish` edge from the operation to that microflow. The source is the operation rather than the service, so `show references to` names the one endpoint instead of the service holding thirty of them, and the edge carries the operation's catalog id, so its path and summary are one join away. Two corrections to the report's diagnosis, both measured. GRAPH_DEAD_ASSETS is kind-agnostic -- it asks only whether any refs row targets the name -- so the edge alone clears it; the comment beside `schedule` in graphRefKinds claiming otherwise was never true (git log -L on the view) and is fixed. impact and SHOW REFERENCES TO do not filter by kind either. `publish` is still added to graphRefKinds, for the analysis graph: without it an API handler is an unreachable root in communities, layers, cycles and centrality. Found while auditing the three vocabularies: `settings` was missing from `show callers`. The project-settings edge shipped in v0.22.0 into refs and into QUAL004 but not into callerRefKinds, so `show callers of <AfterStartupMicroflow>` was still blind to it -- fixed here too. `sync` is deliberately not a caller kind (it names an entity an offline profile downloads, which is a use of a type) and the test now pins it in the excluded set beside `datasource`. Controls: stubbing extractPublishedRestRefs to emit nothing reproduces "reported dead" verbatim, and dropping the empty-microflow guard fails the two-operation test. The dead-assets test carries an unreferenced microflow as its own control. Fixes mendixlabs#1126 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACcuXVKhQTiYp1Suv7KTGY
An entity event handler runs a microflow on every create/commit/delete of
its entity. The catalog reduced the whole list to
CATALOG.ENTITIES.HasEventHandlers — a flag saying some exist and nothing
else — and emitted no `refs` row at all, so every consumer of the
reference graph agreed the handler microflow was unreachable:
show callers of Mod.ACT_Order_Validate -> (no callers found)
CATALOG.GRAPH_DEAD_ASSETS -> lists it
mxcli lint -> [QUAL004] "is not called from
anywhere. Remove if unused"
Same class as the scheduled-event gap and worse in degree: a handler runs
on every commit, so the false "dead" verdict lands on code hotter than
most of what IS reported as live, and the lint suggestion is to delete it.
Both halves land together, as they did for offline sync configs:
* CATALOG.ENTITY_EVENT_HANDLERS — one row per handler with the moment,
the event, the microflow, RaiseErrorOnFalse and PassEventObject. The
distinction the flag lost is not cosmetic: a Before handler with
`raise error` can veto the commit, an After handler cannot.
* An `event` edge in refs (ENTITY -> MICROFLOW), carrying neither the
moment nor the event. refs has no column for them, and a kind per
combination would put eight kinds into every consumer's list to say one
thing.
The three consumers of the reference graph keep independent lists, so the
kind is added to all of them: callerRefKinds, graphRefKinds and the
bundled QUAL004 Starlark rule. CatalogSchemaVersion is bumped 11 -> 12 for
the same reason 11 was: refs are only written by REFRESH CATALOG FULL and
NewFromFile applies the schema with CREATE TABLE IF NOT EXISTS, so without
the bump an existing .mxcli/catalog.db would gain the empty table and keep
serving the pre-fix edge set.
Verified end to end on a project with two handlers, with a control: the
extractor stubbed to emit nothing reproduces "(no callers found)" and two
dead microflows; the fix reports the entity as the caller, no dead assets
and no QUAL004. The same control is inside
TestEventEdgeClearsTheDeadAssetVerdict, and removing the kind from
graphRefKinds or the Starlark rule fails its own guard.
Fixes mendixlabs#1127
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BS9HVaoNfubFxWm53kLwM
…dixlabs#1120 follow-up) `CREATE OR MODIFY MICROFLOW` re-enabled concurrent execution on a microflow that disallowed it — removing the running app's concurrency protection — and dropped the concurrency error message with all its translations, the error microflow, and MarkAsUsed. Two things made this the worst of the three drops in this function, and both are about direction rather than severity. The backend already read AllowConcurrentExecution and MarkAsUsed back; TestMicroflowRoundTrip_ConcurrentExecutionFlags has guarded them since mendixlabs#723 and was green throughout. buildMicroflowFromStmt stamps its own literals over both before the backend is ever called, so the guard was one layer below the bug. When a property is reset, the last writer on the path is the one that matters. And CE4899 fires on disallow-without-a-message, never on allow. mendixlabs#723's bug wrote Go's zero value (allow -> disallow) and hit CE4899 at once; this one writes the opposite (disallow -> allow), so the single error that covers this area is structurally blind to it. A checker that catches a loss in one direction is not coverage for that property. Carried from the stored microflow, with the locals seeded to the new-microflow defaults so a CREATE is unchanged (asserted). The error message reuses the existing textFromGen/textToGen pair, so translations survive; nil still emits the bare empty Texts$Text the writer always wrote. ConcurrentExecutionSettings is marked Deprecated rather than removed — nothing reads or writes it and Mendix stores no thread count, but the type is exported. microflowToGen now sources every property from the model; no hardcoded constants remain. Controls: hardcoding the executor literals back, emptying the writer's pair, and stubbing the reader each fail a different test with the reported symptom. Two measurement traps hit while writing those tests and recorded in the finding: bytes.Equal on two encodes of one microflow always differs (fresh sub-element $IDs), and canon.Equal on a whole microflow always differs too (StableId is a fresh GUID value and Equal does not mask) — compare the sub-element, or use Reconcile. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ssUKiZ9ekBvzSNM5VCVoP
Mendix stores the rollback event as "RollBack" — capital B, confirmed against generated/metamodel (DomainModelsEventRollBack), and disagreeing with every neighbouring enum in that file, where the same word is "Rollback". The value is stored verbatim rather than normalised, so a query spelling it the expected way returns zero rows and not an error. Documented on CATALOG.ENTITY_EVENT_HANDLERS and pinned by a test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012BS9HVaoNfubFxWm53kLwM
The unit tests covered the codec and the executor; nothing had written to
a real .mpr or asked mxbuild what it thought. Running it found two things
`go test` could not.
**The URL fixture described a document Mendix refuses to build.** It
paired `Url: "item/{Key}"` with `UrlSearchParameters: ["….Key"]` — the
same parameter — and mxbuild rejects that as CE5612: a parameter used in
the URL path may not also be a search parameter. The two sets are
disjoint. Nothing in a unit test validates the model, so the fixture was
free to be invalid. Both fixtures now use a distinct `Filter` parameter
and say why; the rule is documented in the microflow skill's pitfalls.
**And the first end-to-end run measured a stale binary.** `bin/mxcli`
predated two of the three commits, because each fix was followed by
`go build ./mdl/...` and `make test` but never `make build`. It produced
a result — URL survived, export level did not — indistinguishable from a
genuine second-read-path defect, and sent me looking for a duplicate
resolver that does not exist.
Measured on mxbuild 11.6.6, Mendix 11.6.6 MPR v2 project, microflow
seeded through mpr.NewWriter + UpdateRawUnit the way Studio Pro would
write it:
seeded control Url="item/{Key}" search=[…Filter] ExportLevel="API"
mx check: 0 errors (the fixture is buildable)
pre-fix binary Url="" search=[] ExportLevel="Hidden"
mx check: 0 errors (the loss is silent — the report)
post-fix binary Url="item/{Key}" search=[…Filter] ExportLevel="API"
mx check: 0 errors (mxbuild accepts what mxcli wrote)
The middle row is the reporter's claim reproduced end to end, and the
last row closes the "verified by construction" gap over the non-empty
UrlSearchParameters by-name list, which nothing had ever built.
Recorded in the findings, including why the repo's own integration gate
did not catch any of this: TestMxCheck_DoctypeScripts skips whenever `mx`
is absent, which is every run in a fresh container.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ssUKiZ9ekBvzSNM5VCVoP
`mxcli run --local` exited 1 on any project with Web UI Settings > OptimizedClient = No, reporting "the build did not produce a client" about a deployment whose client was in the same directory. The gate recognised two shapes — a rollup config to run, or a bundle mxbuild already wrote — and treated everything else as a failed build. A classic (Dojo) deployment is a third: it has neither, because the classic client has no bundling step in any Mendix version. Measured on one blank 11.12.2 app built twice, changing only that setting: mxbuild swaps which client lands in deployment/web/ and parks the other beside it. Yes -> React in web/ (+rollup.config.mjs), Dojo in dojo-web/. No -> Dojo in web/ (index.html loading mxclientsystem/mxui/mxui.js, no rollup config, no dist), React in react-web/ with its own rollup config. Detection reads the deployment's own web/index.html, not the model setting: the deployment is what gets served, the two disagree right after the setting changes, and it covers MigrationMode without predicting what that emits. It tests for the classic client on positive evidence — inferring it from the absence of the React shapes would make every genuinely broken deployment look classic and silently skip the bundle, which is the black screen this gate exists to prevent. The gate had five consumers, not one: the boot bundle, the --watch bundler, the post-boot re-bundle guard, and ensureClientServed, which probes that /dist/index.js is served (measured 404 on a classic app) — so fixing only the boot would have moved the failure to every applied change under --watch. Two of them carried hand-copied duplicates of the same gate and had drifted once already (the 11.14 fix landed on BuildWebClient only), so they now switch on one planWebClient rather than gaining a third copy. Verified end to end on a real 11.12.2 classic app: boots, renders in Chromium with the mx global present and no console errors, and hot-applies a model change under --watch. testdata/webclient/ holds both entry points as mxbuild writes them. Fixes mendixlabs#1123. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019qk7hHmsPXfeEMAGphVA4H
`mxcli new --version 10.24.25` died with a bare 404: HTTP 404 from https://cdn.mendix.com/runtime/mxbuild-10.24.25.tar.gz Mendix 9 and 10 publish FOUR-part artifact names carrying a build number the release notes never mention — the release called 10.24.25 is mxbuild-10.24.25.122571.tar.gz — while Mendix 11 publishes three parts. MxBuildCDNURL interpolates whatever string it is handed and nothing resolved a partial version, so a hand-typed 10.x version named no artifact at all and every probe of one 404'd. That reads as "Mendix 10 is no longer on the CDN" rather than "that is not its name". The CDN is an S3 bucket that answers ListObjectsV2, so what exists is enumerable rather than guessable. ResolveCDNVersion returns a four-part version untouched, returns a version the CDN serves as given (every supported Mendix 11) without a listing call, and otherwise lists runtime/mxbuild-<version>. and takes the highest build. Both the mxbuild and runtime archives follow the same naming, so one resolved string serves both. Wired at the two entry points where a user types a version, `mxcli new` and `mxcli setup mxbuild`. Project-driven paths were never affected: the MPR's _ProductVersion already carries all four parts, and resolving a four-part version is a no-op that does not touch the network. Resolution happens before step 1 of `new` because the resolved string is what the rest of the command must use — it checks the created project's stamp against the requested version, and mx create-project stamps four parts. An unresolvable version now lists what is published for that major.minor instead of showing a 404. Three details are load-bearing and each has a test: the .sha256 sidecar beside every archive must not be picked as an artifact; the listing prefix needs its trailing dot or 10.24.2 swallows 10.24.20..10.24.26; and build numbers are not zero-padded, so a text sort puts 99999 above 122571 and 10.24.9 above 10.24.26. The truncation path is tested too, since a page treated as complete would silently resolve to the wrong build. Verified end to end: `mxcli new Verify1121 --version 10.24.25` now prints "Resolved Mendix 10.24.25 to 10.24.25.122571" and creates a project stamped 10.24.25.122571. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016aHj6mJwKCZD7EX7wcD6jW
…axloot # Conflicts: # .claude/lint-rules/orphaned_elements.star # mdl/catalog/builder.go # mdl/catalog/builder_graph.go # mdl/catalog/builder_references.go # mdl/catalog/lint_rule_vocabulary_test.go # mdl/executor/cmd_search.go # mdl/executor/cmd_search_callers_test.go
fix(run-local): mxbuild resolution and classic-client support for the local loop
…paid for The guidance was not missing. fix-issue.md step 4 already said to verify at the layer the symptom lives in and pointed at verify-in-runtime.md; step 5 already said to prove the test detects the bug. What failed on mendixlabs#1121 was routing and two gaps in the rules themselves. ROUTING. Only .claude/commands/ entries appear in a session's skill listing. The .claude/skills/mendix/<name>/SKILL.md files already carry name/description frontmatter and none of them are listed either — they are for the user projects mxcli init syncs them into, where Claude Code scans .claude/skills/. So a top-level dev skill is a document you have to already know about, and nothing loads it when someone says "investigate issue N". Adds /mxcli-dev:fix-issue as the routed entry point; the skill keeps the mechanics and the command holds the order of work. It is under mxcli-dev/, so it is not synced into user projects. RULE 1 — run the real thing when the argument is about what a Mendix tool accepts. verify-in-runtime.md asks whether the symptom is a property of the running app. That is right for a rendering bug (mendixlabs#812) and does not fire on a version gate: mendixlabs#1121's unit tests were sound and proved what they claimed. What they could not touch is the claim the fix rests on — that writing two 11.5-only properties below 11.5 is unsafe. Building a real 10.24.25 app with the keys forced back in settles it in one run: mxbuild reports 0 errors, so nothing in the toolchain catches them and the guard is load-bearing rather than decorative. Trigger is now explicit, with the two-copies recipe. RULE 2 — "cannot be verified here" is a claim and needs a fix's evidence. It ends the investigation, so it gets the least scrutiny and does the most damage. On mendixlabs#1121 a dozen uniform 404s (with 11.x succeeding on the same host) became "Mendix 10 is not downloadable from this environment", written into a PR body as fact. It was false: Mendix 9 and 10 publish four-part names with a build number. The tell was the shape of the evidence — a negative uniform across an entire class is evidence about the query, not the class. Also fixes the skill's stale tail, which is review.md rows 5 and 7 wearing a package name: it sent tests to sdk/mpr (deleted when the legacy engine went), told the reader to add a row to a symptom table that moved to findings/*.jsonl, and carried a hardcoded /c/users/... go path. Replaced with the current layer→package table and a checklist that includes the two rules. Adds review.md row 24 for the class, since a deletion that leaves its guidance behind is worse than no guidance — the reader trusts it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016aHj6mJwKCZD7EX7wcD6jW
Follow-up to the previous commit, which found fix-issue.md sending tests to
a package deleted with the legacy engine. It was not alone.
The two SYNCED skills were the reason to do this now: mxcli init copies
mendix/debug-bson and mendix/validation-microflows into every user project,
so users were being handed paths that have not existed for two releases.
sdk/mpr/writer_widgets.go -> mdl/backend/modelsdk/widget_write.go
sdk/mpr/writer_microflows.go -> mdl/backend/modelsdk/microflow_write.go
sdk/mpr/writer_entities.go -> mdl/backend/modelsdk/domainmodel_write.go
sdk/mpr/parser_microflow.go -> mdl/backend/modelsdk/microflow_read_actions.go
sdk/mpr/parser_page.go -> mdl/backend/modelsdk/page.go
plus modelsdk/codec/{encoder,decoder}.go named as the layer underneath, which
the old tables never mentioned. Every target was checked to exist before being
written down.
Three things beyond a path rename:
debug-bson told the reader to write a throwaway Go program importing
github.com/mendix/modelsdk-go/sdk/mpr — a module path this repo has never
had, calling an API it does not export. mxcli ships `bson dump` for exactly
this, so the snippet is replaced by the command, including the compare form
that is the actual technique the skill is about.
That command was then documented wrong in five places as `mxcli dump-bson`,
which does not exist, and its `--compare` was shown as two positional
arguments, which the parser rejects — `--compare` is a StringSlice and needs
"A,B". The wrong form came from the command's own --help example, fixed here
too, since leaving it means the next reader copies it again. Every invocation
the skill now contains was run against a real 10.24.25 project.
Two references were a rule outliving its package rather than a stale path:
"the executor must not import sdk/mpr for writes" is now "must not reach past
ctx.Backend", which is what ADR-0002 actually says and stays true after the
next refactor.
Left alone deliberately: docs/plans/, docs/13-decisions/ and docs/11-proposals/
(~300 references). Those are dated records — several describe retiring sdk/mpr
— and rewriting them would falsify the history. ADRs are immutable by
convention. The sweep covers the layer that tells a reader where to work today.
Adds review.md row 25 for the class the CLI examples fell into: a documented
invocation nobody ran, copied from a --help that had the same error.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016aHj6mJwKCZD7EX7wcD6jW
docs(skills): route fix-issue, add the two rules mendixlabs#1121 paid for, sweep stale sdk/mpr guidance
AI Code ReviewLet me review this PR based on the checklist provided. Overview of the PRThis PR appears to be a sync from the "ako/mxcli" fork, bringing in various fixes and improvements grouped by theme:
Review Against ChecklistOverlap & duplication
MDL syntax design (for new or modified MDL syntax)
Full-stack consistency (for MDL features)
Test coverage
Security & robustness
Scope & atomicity
Code quality
Bugs & correctness
What Looks Good
RecommendationThe PR appears to be well-structured, addresses real bugs with appropriate fixes, includes comprehensive tests, and follows the project's architectural patterns. I don't see any critical issues that would prevent merging. Recommendation: Approve the PR However, since this is a large PR with many changes, I should note that in a real review process, I would want to:
But based on the code review alone, the PR looks good to merge. Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
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.
15 commits from the fork's
main, on top of the last sync. Grouped by theme.Microflow properties silently dropped on rewrite (#1120 and follow-ups)
CREATE OR MODIFY MICROFLOWstamped literals over three properties it never read back, so editing only a microflow's body deleted them. All three were invisible tomxcli check,mx checkand mxbuild — the resulting microflow is valid, just diminished.48421a1) — Studio Pro's URL field and its search parameters were reset to empty. Carried at all three layers;DESCRIBEnow emits a-- URL:comment.18306e5) — pinned toHiddenby every rewrite, quietly shrinking a module's public surface. Needs a default as well as a carry, since""is not a member ofMicroflowsExportLevel. Measured against three marketplace modules: 55 of 55 documents storeHidden, so the constant was a default in disguise.29cc3c0) — re-enabled concurrent execution on a microflow that disallowed it, and dropped the error message with its translations. The round-trip test guarding this has been green since Microflow read/write round-trip drops fields, and default auto-layout misplaces elements #723: the executor stamped its literals one layer above the backend. CE4899 only fires in the opposite direction, so the one relevant checker was structurally blind to it.608e4c1) — the first real mxbuild 11.6.6 run found the URL fixture described a document Mendix refuses to build (CE5612: a URL path parameter may not also be a search parameter), and thatbin/mxcliwas stale, producing a result indistinguishable from a second defect.run --localresolution and client supporte60dcf1, fixesrun --localandtest --localstartmxbuild --servefrom the newest cache entry instead of the mxbuild they just resolved, so the build fails with a version mismatch (or a Linux binary on macOS) even though the matching Studio Pro is installed #1122) — bothServeOptionsliterals omittedMxBuildPath, soStartServere-resolved from the cache and on a miss took the newest version there. Guaranteed on macOS, where mxcli's own half-populated cache directory causes the miss. Both call sites now build options through one function; a named version is refused up front rather than substituted.ae6fb39, fixesrun --localcannot boot a project withWeb UI Settings > OptimizedClient = No:BuildWebClientonly recognises the React client layout, and its error claims "the build did not produce a client" when the build produced the classic client #1123) —run --localexited 1 on any project withOptimizedClient = No, reporting "the build did not produce a client" about a deployment whose client was right there. Detection reads the deployment's ownweb/index.htmlon positive evidence. The gate had five consumers, two carrying hand-copied duplicates.--mxbuild-pathonrun(4992ee9, fixesmxcli run --localhas no--mxbuild-pathflag, but the run-local skill installed bymxcli init, the runlocal.go source comment and two error messages say it does #1125) — the flag two error messages and the shipped skill told users to pass answered "unknown flag". The test asserts the invariant: any flag an mxbuild-resolution message advertises must be onerunaccepts.Version resolution and gating
0c576e6) —mxcli new --version 10.24.25died with a bare 404. Mendix 9 and 10 publish four-part artifact names carrying a build number the release notes never mention; 11 publishes three. The CDN answersListObjectsV2, so the build is enumerated rather than guessed. An unresolvable version now lists what is published for that major.minor.15825d0, fixes CREATE PAGE with Params: is refused on Mendix 10 — no way to create a parameterised page #1121) — parameterised pages were refused on every Mendix 10 project. The 11.0 floor was never measured; it matched a proposal's illustrative sample table and nothing else. Measured against the Model SDK's ownStructureVersionInfo: the element is 9.4, only its optional/default-value half is 11.5 — so the registry gates the element and the writer gates the tail. 11.0–11.4 passed the old gate and got both 11.5-only keys written, which is the regression control.Catalog: entry points are no longer reported dead
Two entry-point kinds invoked by the platform had no inbound reference edge, so
show callers of,GRAPH_DEAD_ASSETS, impact and lint rule QUAL004 all agreed the microflow was unreachable — with QUAL004 suggesting deletion.b1860eb, fixes Published REST operations contribute no rows torefs, so every operation microflow reads as dead andSHOW CALLERSreports no callers (read-back gap, not data loss) #1126) — on the reported model, 92 of 93 operations named a microflow and all 92 were listed dead: 15% of the dead list, aimed at the most exposed code in the app. The edge's source is the operation, not the service. Also corrects two claims in the report's diagnosis and adds the long-missingsettingscaller kind.60af71d,a82e40a, fixes Entity event handler microflows have norefsrow, so before/after commit and delete handlers read as dead assets with no callers #1127) — the catalog reduced the whole list to aHasEventHandlersflag. AddsCATALOG.ENTITY_EVENT_HANDLERS(the moment matters: a Before handler withraise errorcan veto a commit, an After handler cannot) and aneventedge. Schema version 11 → 12, since refs are only written byREFRESH CATALOG FULL. Separately pins that Mendix stores the rollback event asRollBack, disagreeing with every neighbouring enum — so a query spelling it the expected way returns zero rows, not an error.Docs and skills
2ee8d83) — the gate list was stated in three places that had drifted;mxcli testwas on none of them, so testing was reachable only by asking for it by name. Now one ordered list in Go, rendered into the generated file, held together by a test that every gate is a real command named in all three.1db507c) — only.claude/commands/entries appear in a session's skill listing, so a top-level dev skill was a document you had to already know about. Adds/mxcli-dev:fix-issue. The two rules: run the real thing when the argument is about what a Mendix tool accepts, and treat "cannot be verified here" as a claim needing evidence — a negative uniform across an entire class is evidence about the query, not the class.sdk/mprswept out of the instructional layer (5351edc) — two of the affected skills are synced into every user project bymxcli init, so users were handed paths that had not existed for two releases. Also replaces a Go snippet importing a module path this repo never had with thebson dumpcommand it exists for, and fixes that command's own--helpexample, which was wrong in five places. Dated records (proposals, ADRs, plans) are deliberately left alone